谈谈你对分库分表的理解?
1、分库
分库是将同一个表中的记录行拆分到多个结构相同的表中。分库目的是减轻单台MySQL实例存储压力及可扩展性。
2、分表
分表也叫做垂直切分,是将一张表按列切分成多个表。当表中的列过多时,就可以按列的相关性或使用频繁程度进行切分。分表目的是解决单张表数据过大后查询的瓶颈问题。
带来的问题
问题一:分布式唯一ID(大型项目中,mysql的主键需要全局唯一怎么办?)
在单库单表的时候,我们使用表的自增ID就可以保证id的唯一性,但是分库分表后一张表被拆分,此时自增id就不能保证唯一性了,因此需要新的方案。
方案一:UUID
优点:JDK自带的工具类,本地生成,性能高
缺点:不适合作为MySQL主键。
- 无序性到导致磁盘随机IO、页分裂等问题。具体的,MySQL推荐使用单调递增的数字作为主键,因为MySQL主键使用的是聚簇索引,会把相邻主键的数据放在相邻的物理存储位置上,当逐渐单调递增时,每次只需要简单的将数据追加到索引的后面即可,类似于顺序写磁盘;而如果主键像UUID那样是无序的,则可能需要将数据插入到之前已有的数据中间,如果插入位置所在的数据页不在内存中,就需要先从磁盘读到内存中,导致磁盘的随机IO,IO压力巨大。同时如果该数据页的空间不足,则可能产生页分裂,需要移动大量数据。
- MySQL的普通索引需要存储主键索引的值,而UUID长度为36的字符串,更占用存储空间,会导致B+树变高,IO次数变多。
方案二:额外维护一个顺序表
在数据库中建立一个Sequence表,表的结构类似于:
CREATE TABLE `SEQUENCE` (
`tablename` varchar (30) NOT NULL ,
`nextid` bigint (20) NOT NULL ,
PRIMARY KEY (`tablename`)
) ENGINE=InnoDB
每当需要为某个表的新纪录生成ID时就从Sequence表中取出对应表的nextid,并将nextid的值加1后更新到数据库中以备下次使用。此方案也较简单,但缺点同样明显:由于所有插入任何都需要访问该表,该表很容易成为系统性能瓶颈,同时它也存在单点问题,一旦该表数据库失效,整个应用程序将无法工作。有人提出使用Master-Slave进行主从同步,但这也只能解决单点问题,并不能解决读写比为1:1的访问压力问题。
方案三:修改自增的起点与步长
默认自增id每次加1,当有多台机器时可以把步长设定为机器的数量值。
比如5台数据库,第一台机器主键从1开始每次加5,第二台从2开始每次加5,以此类推,从而保证主键的全局唯一。
-- 设置序列的增长值
set global auto_increment_increment=5;
缺点就是机器的数量可能不固定。
问题二:分布式事务
在分库分表前,全部的表都在同一个库里,可以用本地事务来保证数据的正确性;分库分表后,需要引入分布式事务。
方案一:两阶段提交。

核心思想是将事务分为两个阶段:第一个阶段是协调者首先询问所有事务的参与者是否可以执行事务的提交操作;第二阶段时协调者根据大家的返回结果决定是否提交操作。如果大家都返回同意,就向所有参与者发送事务提交请求,否则向所有参与者发送事务中断回滚请求。
- 优点:流程简单
- 缺点:存在同步阻塞、协调者单点出错等问题
结论:在实际的高并发业务中,一般都不会使用强一致性的分布式事务(金融场景是个特例),更多的是通过各种各样的的手段保证最终的一致性(柔性事务)。
常见的手段:回滚、重试、监控、告警、人工对账等等。具体的,如果事务中的某一步出错,就回滚,为了避免网络抖动等情况导致回滚失败,一般都会有回滚重试的流程,但重试一般有次数上限,如果达到上限还是不行可能就是其他问题,比如代码的bug,这种情况再重试也没有用。因此重试达到上限后需要发送告警,人为介入排查。
不使用强一致性事务的原因:
- 会带来严重的性能损耗,导致请求响应时间增加,系统吞吐量下降,用户体验变差。
- 实际业务中由于部分失败导致数据不一致的场景发生的概率比较低,可能投入比收益要大。
- 引入额外的复杂度,开发和维护的成本比较高。
问题三:跨库查询
数据被分到不同的表上,导致原本在同一页的数据被分走了。
方案一:选择合适的分表字段
这是最重要的,如果选择合理,可以很大程度的避免这种问题
方案二:使用搜索引擎,例如ES
ES只存储需要进行搜索的字段,查询完ES后再根据关键字段去数据库中查询完整的数据。
方案三:分开查询 / join联合查询
1、用两条sql语句。先查一个表,根据查出来的id再查一个
2、用一条sql语句。join语句,关联查询,一次查出两个表。
-
关联查询的优点:对于该功能,效率是高的,因为只需要一次数据库查询动作。
-
关联查询的缺点:功能上会有耦合。具体来说,对于当前的功能,是方便的,但对于其他只想查出其中第一个表的功能,也得用这个关联sql语句,那就会查出来两张表,就是多余的了。另外数据库资源相对于服务器资源来说更宝贵,更容易成为链路中的瓶颈。
查两次的方法效率是低,但是可以借助redis来缓解此问题,把影响效率的数据,缓存到redis上。另外没有耦合,可能更好用一些
www.bilibili.com/video/BV1aY… mp.weixin.qq.com/s/X7ciEPZWL… blog.cnkj.site/Interview/I…