MySQL分库分表方案,什么时候需要分表
前言
做网站的同学肯定都遇到过这种情况:网站用户越来越多,数据库里的表数据量越来越大,查询越来越慢,插入也越来越慢。这时候怎么办?很多同学第一反应就是:分库分表!
但是分库分表不是万能药,也不是说只要数据量大了就一定要分。很多同学上来就分库分表,结果把自己搞的更累了,问题反而更多。
这篇文章就把分库分表的知识点从头到尾讲清楚:什么时候需要分库分表?怎么分?分完之后有什么问题?看完之后你就知道什么时候该分,什么时候不该分了。
一、什么时候需要分库分表
很多同学一上来就问:分库分表好不好?其实这是个伪命题。分库分表是有代价的,不是什么场景都适合。
一般来说,出现下面这些情况的时候,才考虑分库分表:
1. 单表数据量太大
一般来说,MySQL单表数据量到了千万级之后,性能就开始下降了。如果到了亿级,那基本上就必须分了。
但是也不是说千万级就一定要分,要看你的表结构和查询。如果表结构简单,查询也都走索引,几千万条数据性能还是能接受的。
2. 单库压力太大
一个数据库实例能承受的连接数、IOPS、CPU都是有限的。如果单库的QPS太高,扛不住了,这时候就需要分库,把压力分散到多个数据库实例上。
3. 业务发展到一定阶段
很多互联网公司的业务发展到一定阶段,单库单表确实扛不住了,这时候才需要分。创业初期的小项目,根本没必要分库分表,纯属给自己找麻烦。
总结一下: 分库分表是最后的手段,不是第一选择。在分之前,先看看能不能通过优化SQL、加索引、读写分离、加缓存这些手段解决问题。如果这些手段都用了还是不行,再考虑分库分表。
二、分库分表的两种方式
分库分表有两种方式:垂直拆分和水平拆分。
1. 垂直拆分
垂直拆分又分两种:
垂直分库
按照业务把不同的表分到不同的数据库里。比如把用户相关的表放到user库,订单相关的表放到order库,商品相关的表放到product库。
优点:
- 业务解耦,不同业务之间互不影响
- 可以针对不同的业务做不同的优化
缺点:
- 跨库联表查询比较麻烦
- 分布式事务问题
垂直分表
把一张大表的字段拆到两张表里。比如把不常用的字段拆到另一张表里,常用的字段留在原表里。
比如一张用户表,有基本信息和详细信息。把基本信息(id、昵称、头像)放到user表,详细信息(简介、地址、备注)放到user_detail表。
优点:
- 减少单表的数据量,查询更快
- 把热点字段和冷门字段分开
缺点:
- 查询的时候可能要联表
- 代码复杂度变高
2. 水平拆分
水平拆分就是把同一张表的数据,按照某个规则分到多张表里。每张表的结构是一样的,只是数据不一样。
比如一张user表有1000万条数据,我们把它分成4张表:user_0、user_1、user_2、user_3。按照用户id取模,0的放user_0,1的放user_1,以此类推。
优点:
- 单表数据量小了,性能上去了
- 可以分散到多个数据库实例上
缺点:
- 跨片查询很麻烦
- 分布式事务问题
- 扩容麻烦
三、怎么选择分片键
水平拆分的时候,最关键的就是选分片键。分片键选不好,后面会很痛苦。
好的分片键有什么特点
- 数据分布均匀:不要有的分片数据特别多,有的特别少
- 查询都能带上分片键:大部分查询都能根据分片键定位到具体的分片,不用扫全部分片
- 尽量不要跨片:尽量避免需要跨片查询的场景
常见的分片键选择
1. 用user_id做分片键
如果你的业务大部分都是跟用户相关的,比如用户的订单、用户的评论,那用user_id做分片键就很合适。同一个用户的数据都在同一个分片上,查询的时候不用跨片。
2. 用order_id做分片键
如果是订单系统,大部分查询都是按订单id查的,那就用order_id做分片键。
3. 按时间做分片
如果是日志类的数据,按时间查询比较多,那就按时间做分片。比如一个月一张表,或者一个季度一张表。
分片算法选什么
常见的分片算法有:
1. 取模
比如:user_id % 4,这样数据会比较均匀。
优点: 数据分布均匀
缺点: 扩容的时候很麻烦,要重新对所有数据做取模,大部分数据都要迁移
2. 范围分片
比如:id在1-1000万的放第一个分片,1000万-2000万的放第二个分片。
优点: 扩容简单,直接加新的分片就行
缺点: 数据分布不均匀,新的数据都写到新的分片上,容易出现热点
3. 一致性哈希
一致性哈希可以解决扩容的时候数据迁移太多的问题。
四、分库分表带来的问题
分库分表不是银弹,分完之后会带来很多新的问题。
1. 分布式事务
原来一个事务里操作同一张表,现在跨了多个分片,原来的本地事务就不管用了,需要用分布式事务。分布式事务的复杂度和性能损耗都是很大的。
2. 跨片查询
原来一条SQL就能查出来的数据,现在要从多个分片查出来再合并。比如要统计所有用户的总数,就得查所有分片然后加起来。如果要分页就更麻烦了。
3. 跨片联表
原来两张表联查很简单,现在两张表在不同的分片上,联查就很麻烦。
4. 全局唯一ID
原来用自增主键就行了,现在分了多个表,自增主键就重复了。需要用全局唯一ID,比如雪花算法。
5. 扩容麻烦
一开始分了4个表,后来数据量太大了,要扩成8个表。这时候数据要重新分布,迁移量很大。
五、常用的分库分表中间件
自己写分库分表的逻辑太麻烦了,一般都是用现成的中间件:
1. ShardingSphere
Apache的开源项目,国内用的比较多。支持分库分表、读写分离、分布式事务这些功能。
2. MyCat
也是一个开源的分库分表中间件,用的也比较多。
3. 官方方案
MySQL本身也有一些分区的功能,比如Range分区、List分区。但是这个是在单库内的分区,不是真正的分库分表。
常见坑
坑1:一上来就分库分表
很多同学刚做项目,就想着分库分表,搞的很复杂。其实小项目根本没必要,单库单表就能扛住。分库分表是等到数据量真的大了再做的事情,不要提前过度设计。
坑2:分片键选的不好
很多同学分片键随便选一个,结果大部分查询都带不上分片键,每次都要扫全部分片,性能反而更差了。分片键一定要选那些查询最常用的字段。
坑3:用取模算法,扩容的时候崩溃了
很多同学一开始用取模算法分了4个表,后来要扩到8个表,结果发现数据全都要重新分布,迁移量巨大,欲哭无泪。所以一开始就要考虑好扩容的问题。
坑4:分完之后跨片查询太多
很多同学分完库分完表之后,发现原来的很多查询都要跨片,性能反而比原来更差了。这就是分片键没选好,或者业务设计的问题。
坑5:以为分库分表是万能的
很多同学觉得分库分表能解决所有性能问题,其实不是。分库分表只是把单表的数据量变小了,但是复杂SQL、慢SQL这些问题还是存在的。优化SQL、加索引这些基础工作还是要做。
总结
MySQL分库分表的核心知识点:
- 分库分表是最后的手段,不是第一选择,先试试优化SQL、加索引、读写分离、加缓存
- 什么时候需要分:单表千万级以上、单库压力太大、业务真的到那个规模了
- 垂直拆分:按业务拆库、按字段拆表
- 水平拆分:同一张表按某个规则拆成多张表
- 分片键很重要:要数据分布均匀、查询都能带上、尽量不要跨片
- 分片算法:取模(均匀但扩容麻烦)、范围(扩容简单但容易有热点)、一致性哈希
- 分完之后的问题:分布式事务、跨片查询、跨片联表、全局唯一ID、扩容麻烦
- 常用中间件:ShardingSphere、MyCat
记住:分库分表是有代价的,能不分就不分。真的要分的时候,一定要想清楚分片键和分片算法,不然后面会很痛苦。
遇到问题加QQ23979811 协助处理