MySQL 的分库分表方案以及带来的一些问题

摘要:mysql 的分库分表方案以及带来的一些问题,分库分表的优缺点,分库分表的策略,一般都是先水平拆分,然后再垂直拆分。以及分库分表带来的问题,比如分布式全局唯一 id,跨库 join 的问题,分布式事务等等,以及针对这些问题的一些解决方案。

为什么要分库分表

数据库中的数据量不一定是可控的,随着时间和业务的发展,在未进行分库分表的情况下,库中的表会越来越多,表中的数据量也会越来越大,相应得数据操作、增删改查的开销也会越来越大。但是一台服务器的资源(cpu、磁盘、内存、io 等)是有限的,最终数据库所能承载的数据量、数据处理能力都将遭遇瓶颈。这时候我们就需要对库和表进行拆分,来优化查询速度和提高数据的并发量。拆分后单表的并发能力提高了,磁盘 i/o 性能也提高了,因为查寻一次所花的时间变短了,如果出现高并发的话,总表可以根据不同的查询,将并发压力分到不同的小表里面。 具体的数据库和数据表的拆分要根据实际的库和表耦合的情况制定一套方案。


分库分表的优缺点

优点,除了上面阐述缓解单台数据库、数据表和服务器的压力之外,还有就是分库分表后可以降低数据库的耦合度,还可以进行主从复制、读写分离等等,解决单台服务器性能不够,成本过高的问题。

缺点,其实相比分库分表带来的优点,我觉得他带来的缺点和对这些缺点的应对解决方案更重要。虽说分库分表带来的问题很多很多,但是又不得不去分库分表。我们总是用另一个方案来弥补当前方案带来的不足。他的缺点比如跨库 join、全局唯一 id、分布式事务、多台数据库访问的问题,还会使你的 sql 和业务逻辑变的异常的复杂,本来能一个 sql 解决的问题,需要你查询多次等等。


分库分表的策略

关于分库分表一般采取两种方案,一种横切,一种纵切,也叫水平拆分,垂直拆分。一般来讲我们都是先横切再纵切,因为纵切的复杂度是要比横切的复杂度高。但是拆分的方案要根据网站的业务设计和数据库结构的设计去拆分,不过常见的拆分方案也有几种。

1、根据用户名 hash 切分,比如按照用户名最有一位进行拆分,用户名最后一位是什么,就将他放在哪个表中,比如 user_a、user_b、user_0、user_1... 等等,但是这样无法保证 id 的唯一性。

2、根据 id 进行 hash 切分,比如根据全局唯一生成的 id 进行取模分到不同的数据库中,然后再根据 id 取模分到不同的表中,此方法可以保证全局 id 唯一性的问题。

3、范围切分,可以是 id 范围,比如 id 在范围 1~1000、1001~2000 会存在不同的表中,也可以按照时间范围,比如一个表存储半年的数据,超过就到下一个表中。

具体的策略要切合业务逻辑和数据库结构设计去考虑,在分库分表过程中,我们要考虑如何既能把数据库拆分,又不能让数据库变得复杂,数据库设计的复杂带来的问题就是查询的复杂。查询的复杂就会直接影响查询性能和用户体验度。如果分库分表没有分好的话,带来的直接问题就是更多的跨库、跨表查询。

比如我们可以把业务相近的订单表、商品表、商品浏览表等等,这种相同维度的表放到一个数据库中。或者在分表的过程中我们可以用一个冗余字段或者少量的冗余字段避免跨表 join 等等的问题。


数据库、表的横切和纵切方案

数据库的横切,比如我们可以把用户库分为 user_db_1、user_db_2... 这种,商品库分为 shopping_db_1、shopping_db_2...这种。

数据库的纵切,一般如果网站的数据库比较多,每个库所对应的业务场景也不同。比如用户表 user、订单表 order、登录日志表 log、商品表 shopping 等,我们可以把不同的业务对应的表放入到不同的库中,然后不同的库可以放到不同的服务器上。


数据表的横切,横切就是将一个数据表中的数据,根据一定的规则计算,然后将数据平均分配到一定数量的一模一样的表结构中,比如 user_table 表可以分成 user_table_1、user_table_2... 数据表,切分完成以后表结构并没有改变,只是一个表的数据分落到了多个表中。

数据表的纵切,通常是按照数据表中的列密集程度进行切分,也可以将经常使用的列和不经常使用的列进行切分。比如我们把问题表中,经常用于搜索问题的几个字段单独拿出来放到一个表中。这样拆分完成后,表结构发生了改变,但是数据量没有变。


问题 1:分配用户访问

分库分表后,用户访问那个表或者那个库就是一个问题,我们怎么样让用户能够像负载均衡那样,随机均衡的去访问数据库或数据表。

1、最简单的方法是在读取数据库配置的时候,利用随机数的算法,将客户端随机的分配到指定的数据库上。

2、利用数据库中间件,常见的中间件有 cobar、tddl、atlas、sharding+jbdc、mycat 等等。


问题 2:跨库、跨表查询

分库后分表后,数据库分布在不同的服务器上,某些业务场景可能需要跨库进行查询获取数据,但是一般基于架构的规范和性能、安全性的考虑,一般是禁止跨库 join 的。

1、在业务中逻辑中将 sql 拆开执行,在代码中实现伪 join,有时候业务中实现的跨库跨表查询,反而比写一个复杂的查询效率更高。

2、字段冗余,比如将一些可能要通过 join 获取的字段放到本表中。

3、要 join 的库能不能放在一个实例,即便是分表了能不能按相同维度分到一起。

4、设计数据库的时候尽量避免这种情况。


问题 3:ID 全局唯一的问题

id 全局唯一的问题,有的分库分表中会出现,有的不会出现,具体要视方案而定。比如用户登录 log 表,如果按照用户名来分库分表的话,就不用在意全局 id 的问题。但是假如你要是把用户表横切的话,就需要注意 id 全局唯一的问题。

1、如果 id 没有必要使用整型的话,可以使用字符串作为 id,利用 uuid() 函数生成一个 36 位长度的字符串,例如:d48644cc-2c7a-4714-9900-18ab99e80b03,这样实现起来比较简单,也不会重复,但缺点是字符串作为 id 占用空间大,索引效率比整型低。

2、类似 twitter 的 snowflake 算法(雪花算法),它给每台机器分配一个唯一标识,然后通过时间戳 + 机房 id + 机器  id + 自增,实现全局唯一 id。这种方式好处在于 id 生成算法完全是一个无状态机,无网络调用,高效可靠。

3、最笨的方法是需要一个基于全局主键的数据库,每次要插入数据时,先插入这个数据库中一条数据,拿到数据库返回的自增 id,然后再去执行接下来的逻辑,这种方案简单好处理,但缺点是利用数据库生成的唯一 id 方案并发量比较低,如果业务场景并发量比较低可以采取这种方案。

4、关于每秒生成几千几万的 id 数量,这样的一个高吞吐量的场景,我们可以提前每次获取一批 id,然后放入到缓存中,比如放入到 redis 的 list 数据结构中,然后利用 redis 的单进程机制,每次从 list 中获取一个 id 进行操作,如果队列中的 id 少于一半后者一定的数量,再去触发拉取一次。


参考资料

mysql分表那些事

Mysql分表和分区的区别、分库分表介绍与区别(转)

大众点评订单系统分库分表实践

服务端指南 数据存储篇 | MySQL(09) 分库与分表带来的分布式困境与应对之策

结束语:感谢您对本网站文章的浏览,欢迎您的分享和转载,但转载请说明文章出处。
Top