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

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

MySQL 分库分表和主从复制系列文档

MySQL 的分库分表方案以及带来的一些问题(本文)

MySQL 分布式事务使用

MySQL 主从复制原理和主从复制(延迟 / 不一致)的解决方案

MySQL 主从复制配置与搭建


为什么要分库分表

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


分库分表的优缺点

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

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


横切与纵切的特性

横切:按照用户名或者 id 等字段,按照一定规则,将一个库(表)的数据拆分到多个库(表)中,以降低单库(表)的大小,达到提升性能的方法。水平切分后,每个库(表)的特点如下:

● 每个库(表)的结构都一样。

● 每个库(表)的数据不一样,没有交集。

● 所有库(表)的并集就是全部的数据。

纵切:将一个字段较多,一个行数据较大的表,按照一定规则,将不同的字段属性拆分到不同的库(表)中,以降低单库(表)的大小,达到提升性能的方法。垂直拆分后,每个库(表)的特点如下:

● 每个库(表)的结构不一样。

● 每个库(表)的字段至少有一列交集,比如 id,因为需要该字段将所有数据关联起来。

● 所有(库)表并集就是全部数据。


横切与纵切的策略

关于分库分表一般采取两种方案,一种横切,一种纵切,也叫水平拆分,垂直拆分。一般来讲我们都是先横切再纵切,因为纵切的复杂度是要比横切的复杂度高。数据库和表的拆分是肯定要做的,但是什么时候拆分呢?这个具体要看公司的 DBA 怎么做了。一般都是单库 / 单表的数据量到达一定程度,或者是业务上的变动需要对表结构进行调整,都需要进行拆分,但是拆分的同时要讲究策略和方案,因为拆分会带来很多问题。

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

比如分库分表没有分好的话,带来的直接问题就是更多的跨库、跨表查询。对于这样的问题,我们可以把业务相近的订单表、商品表、商品浏览表等等,这种相同维度的表放到一个数据库中。或者在分表的过程中我们可以用一个冗余字段或者少量的冗余字段避免跨表 join 等等的问题。


横切和纵切的方案

虽然说拆分方案要具体到网站的业务场景和数据库(表)中,但是常见的拆分方案也有几种:

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

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

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

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


问题一:分配用户访问

分库分表后,数据库 / 表就相当于 nginx 负载均衡后面的分发节点一样。我们怎么让 mysql 像 nginx 负载均衡那样,把用户的请求随机均衡分发到指定的数据库 / 表中?

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

2. 还有一种办法就是利用数据库中间件,常见的中间件有 cobar、tddl、atlas、sharding+jbdc、mycat 等等。


问题二:跨库 / 跨表查询

分库分表后,数据库 / 表分布在不同的数据库中,甚至有可能分布在不同的服务器节点上,这种情况我们怎么应对跨库 / 表查询?或者是怎么进行跨表统计?某些业务场景可能需要跨库查询,但是一般基于架构的规范和性能、安全性的考虑,一般是禁止跨库 join 的。

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

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

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

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


问题三:ID 全局唯一

id 全局唯一的问题,有的分库分表中会出现,有的不会出现,具体要视方案而定。比如用户登录 log 表,如果按照用户名来分库分表的话,就不用在意全局 id 的问题。但假如你要是按照用户 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 少于一半后者一定的数量,再去触发拉取一次。


问题四:分布式事务

分布式事务也可以称为“跨库事务”,只要在本库中的事务才是单机事务或者本地事务。随着数据库的拆分,分布式事务是必须要面对的一个问题,因为分布式事务也要保证事务的原子性。使用分布式事务要注意两个概念就是“两阶段提交”和“cap 理论”。

其实随着分库与分表的演变,一定会遇到分布式事务问题,那么如何保证数据的一致性就成为一个必须面对的问题。目前,分布式事务并没有很好的解决方案,难以满足数据强一致性,一般情况下,使存储数据尽可能达到用户一致,保证系统经过一段较短的时间的自我恢复和修正,数据最终达到一致。


参考资料

mysql分表那些事

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

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

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

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