(本文待完善)
要关注的一些问题:
- 保证每个表都有数据
- 数据倾斜问题(数据均匀分布和数据聚合很可能是冲突的)
- 分布式事务
- 唯一性保证
- 业务隔离:不同的业务用不同的库
- 如何按时间分表
一种错误的分库分表方案
假设某业务要记录用户相关的一些数据,准备用用户标识 user_id(long 类型,MySQL 中是 bigint )进行分库分表。方案如下:
- 库表数量:4个库,每个库8张表,标号从0开始。
- 分库方案:user_id % 4
- 分表方案:user_id % 8
这个分表方案是错误的。因为部分表,会永远没有数据。
代码验证:
使用 Python 3 验证。
stats = {}
for db_index in range(0, 4):
for tb_index in range(0, 8):
stats.setdefault(db_index, {})
stats[db_index].setdefault(tb_index, 0)
for user_id in range(1, 200000):
db_index = user_id % 4
tb_index = user_id % 8
stats.setdefault(db_index, {})
stats[db_index].setdefault(tb_index, 0)
stats[db_index][tb_index] += 1
for db_index in range(0, 4):
for tb_index in range(0, 8):
print('分库号:{}, 分表号:{}, 数据量:{}'.format(db_index, tb_index, stats[db_index][tb_index]))
运行结果:
分库号:0, 分表号:0, 数据量:24999
分库号:0, 分表号:1, 数据量:0
分库号:0, 分表号:2, 数据量:0
分库号:0, 分表号:3, 数据量:0
分库号:0, 分表号:4, 数据量:25000
分库号:0, 分表号:5, 数据量:0
分库号:0, 分表号:6, 数据量:0
分库号:0, 分表号:7, 数据量:0
分库号:1, 分表号:0, 数据量:0
分库号:1, 分表号:1, 数据量:25000
分库号:1, 分表号:2, 数据量:0
分库号:1, 分表号:3, 数据量:0
分库号:1, 分表号:4, 数据量:0
分库号:1, 分表号:5, 数据量:25000
分库号:1, 分表号:6, 数据量:0
分库号:1, 分表号:7, 数据量:0
分库号:2, 分表号:0, 数据量:0
分库号:2, 分表号:1, 数据量:0
分库号:2, 分表号:2, 数据量:25000
分库号:2, 分表号:3, 数据量:0
分库号:2, 分表号:4, 数据量:0
分库号:2, 分表号:5, 数据量:0
分库号:2, 分表号:6, 数据量:25000
分库号:2, 分表号:7, 数据量:0
分库号:3, 分表号:0, 数据量:0
分库号:3, 分表号:1, 数据量:0
分库号:3, 分表号:2, 数据量:0
分库号:3, 分表号:3, 数据量:25000
分库号:3, 分表号:4, 数据量:0
分库号:3, 分表号:5, 数据量:0
分库号:3, 分表号:6, 数据量:0
分库号:3, 分表号:7, 数据量:25000
可以看到,
- 分库0只要分表0、分表4有数据;
- 分库1只有分表1、分表5有数据;
- 分库2只有分表2、分表6有数据;
- 分库3只有分表3、分表7有数据;
数学证明
以下均为整数运算:
以1号库为例, 某 user_id 落库到 1 号库,即:
user_id % 4 = 1
那么一定有整数 k,使得 user_id = 4 * k + 1
。
所以,分表号 m 是:
m = user_id % 8
= ( 4 * k + 1 ) % 8
= 4 * k + 1 - [ ( 4 * k + 1)/ 8 ] * 8
= 4 * k + 1 - [ ( 4 * k + 1 ) / 8 ] * 4 * 2
= 1 + 4 * k - 4 * [ ( 4 * k + 1 ) / 8 ] * 2
= 1 + 4 * { k - [ ( 4 * k + 1 ) / 8 ] * 2 }
所以,分表号一定是 4的倍数 + 1 。
正确的做法:
方案1:
采用同一字段取模分库分表时,分库数和分表数的最大公约数需要是1,否则会造成数据倾斜甚至部分表无数据。 例如: 库表数量:4个库,每个库31张表,标号从0开始。 分库方案:user_id % 4 分表方案:user_id % 31
方案2:
分库用一个字段,分表用另外一个字段。
方案3:
分库和分表不要用相同的计算方法。 比如分表改成先 crc32 ,再模 8 ,也就是:
- 分库方案:user_id % 4
- 分表方案:crc32(user_id) % 8
验证代码如下:
from zlib import crc32
stats = {}
for db_index in range(0, 4):
for tb_index in range(0, 8):
stats.setdefault(db_index, {})
stats[db_index].setdefault(tb_index, 0)
for user_id in range(1, 200000):
db_index = user_id % 4
tb_index = crc32('{}'.format(user_id).encode('utf-8')) % 8
stats.setdefault(db_index, {})
stats[db_index].setdefault(tb_index, 0)
stats[db_index][tb_index] += 1
for db_index in range(0, 4):
for tb_index in range(0, 8):
print('分库号:{}, 分表号:{}, 数据量:{}'.format(db_index, tb_index, stats[db_index][tb_index]))