移动端网站建设 新闻动态,给别人做金融网站 犯法吗,重庆市建设工程交易中心,深圳市房地产交易中心官网文章目录主键选择索引设计全局表唯一索引总结结语主键选择
对主键来说#xff0c;要保证在所有分片中都唯一#xff0c;它本质上就是一个全局唯一的索引。如果用大部分同学喜欢的自增作为主键#xff0c;就会发现存在很大的问题。
因为自增并不能在插入前就获得值#xf…
文章目录主键选择索引设计全局表唯一索引总结结语主键选择
对主键来说要保证在所有分片中都唯一它本质上就是一个全局唯一的索引。如果用大部分同学喜欢的自增作为主键就会发现存在很大的问题。
因为自增并不能在插入前就获得值而是要通过填 NULL 值然后再通过函数 last_insert_id()获得自增的值。所以如果在每个分片上通过自增去实现主键可能会出现同样的自增值存在于不同的分片上。
比如对于电商的订单表 orders其表结构如下分片键是o_custkey表的主键是o_orderkey
CREATE TABLE orders (O_ORDERKEY int NOT NULL auto_increment,O_CUSTKEY int NOT NULL,O_ORDERSTATUS char(1) NOT NULL,O_TOTALPRICE decimal(15,2) NOT NULL,O_ORDERDATE date NOT NULL,O_ORDERPRIORITY char(15) NOT NULL,O_CLERK char(15) NOT NULL,O_SHIPPRIORITY int NOT NULL,O_COMMENT varchar(79) NOT NULL,PRIMARY KEY (O_ORDERKEY),KEY (O_CUSTKEY)......
) ENGINEInnoDB如果把 o_orderkey 设计成上图所示的自增那么很可能 o_orderkey 同为 1 的记录在不同的分片出现如下图所示 所以在分布式数据库架构下尽量不要用自增作为表的主键自增性能很差、安全性不高、不适用于分布式架构。
讲到这儿我们已经说明白了“自增主键”的所有问题那么该如何设计主键呢依然还是用全局唯一的键作为主键比如 MySQL 自动生成的有序 UUID业务生成的全局唯一键比如发号器或者是开源的 UUID 生成算法比如雪花算法但是存在时间回溯的问题。
总之用有序的全局唯一替代自增是这个时代数据库主键的主流设计标准如果你还停留在用自增做主键或许代表你已经落后于时代发展了。
索引设计
通过分片键可以把 SQL 查询路由到指定的分片但是在现实的生产环境中业务还要通过其他的索引访问表。
还是以前面的表 orders 为例如果业务还要根据 o_orderkey 字段进行查询比如查询订单 ID 为 1 的订单详情
SELECT * FROM orders WHERE o_orderkey 1我们可以看到由于分片规则不是分片键所以需要查询 4 个分片才能得到最终的结果如果下面有 1000 个分片那么就需要执行 1000 次这样的 SQL这时性能就比较差了。
但是我们知道 o_orderkey 是主键应该只有一条返回记录也就是说o_orderkey 只存在于一个分片中。这时可以有以下两种设计
同一份数据表 orders 根据 o_orderkey 为分片键再做一个分库分表的实现在索引中额外添加分片键的信息。
这两种设计的本质都是通过冗余实现空间换时间的效果否则就需要扫描所有的分片当分片数据非常多效率就会变得极差。
而第一种做法通过对表进行冗余对于 o_orderkey 的查询只需要在 o_orderkey 1的分片中直接查询就行效率最高但是设计的缺点又在于冗余数据量太大。
所以改进的做法之一是实现一个索引表表中只包含 o_orderkey 和分片键 o_custkey如
CREATE TABLE idx_orderkey_custkey o_orderkey INTo_custkey INT,PRIMARY KEY (o_orderkey)
)如果这张索引表很大也可以将其分库分表但是它的分片键是 o_orderkey如果这时再根据字段 o_orderkey 进行查询可以进行类似二级索引的回表实现先通过查询索引表得到记录 o_orderkey 1 对应的分片键 o_custkey 的值接着再根据 o_custkey 进行查询最终定位到想要的数据如
SELECT * FROM orders WHERE o_orderkey 1# step 1
SELECT o_custkey FROM idx_orderkey_custkey
WHERE o_orderkey 1# step 2
SELECT * FROM orders
WHERE o_custkey ? AND o_orderkey 1这个例子是将一条 SQL 语句拆分成 2 条 SQL 语句但是拆分后的 2 条 SQL 都可以通过分片键进行查询这样能保证只需要在单个分片中完成查询操作。不论有多少个分片也只需要查询 2个分片的信息这样 SQL 的查询性能可以得到极大的提升。
通过索引表的方式虽然存储上较冗余全表容量小了很多但是要根据另一个分片键进行数据的存储依然显得不够优雅。
因此最优的设计不是创建一个索引表而是将分片键的信息保存在想要查询的列中这样通过查询的列就能直接知道所在的分片信息。
如果我们将订单表 orders 的主键设计为一个字符串这个字符串中最后一部分包含分片键的信息如
o_orderkey stringo_orderkey o_custkey那么这时如果根据 o_orderkey 进行查询
SELECT * FROM Orders
WHERE o_orderkey 1000-1;由于字段 o_orderkey 的设计中直接包含了分片键信息所以我们可以直接知道这个订单在分片1 中直接查询分片 1 就行。
同样地在插入时由于可以知道插入时 o_custkey 对应的值所以只要在业务层做一次字符的拼接然后再插入数据库就行了。
这样的实现方式较冗余表和索引表的设计来说效率更高查询可以提前知道数据对应的分片信息只需 1 次查询就能获取想要的结果。
这样实现的缺点是主键值会变大一些存储也会相应变大。但只要主键值是有序的插入的性能就不会变差。而通过在主键值中保存分片信息却可以大大提升后续的查询效率这样空间换时间的设计总体上看是非常值得的。
当然这里我们谈的设计都是针对于唯一索引的设计如果是非唯一的二级索引查询那么非常可惜依然需要扫描所有的分片才能得到最终的结果如
SELECT * FROM Orders
WHERE o_orderate ? o_orderdate ?因此再次提醒你分布式数据库架构设计的要求是业务的绝大部分请求能够根据分片键定位到 1 个分片上。
如果业务大部分请求都需要扫描所有分片信息才能获得最终结果那么就不适合进行分布式架构的改造或设计。
最后我们再来回顾下淘宝用户订单表的设计 上图是我的淘宝订单信息可以看到订单号的最后 6 位都是 308113所以可以大概率推测出
淘宝订单表的分片键是用户 ID淘宝订单表订单表的主键包含用户 ID也就是分片信息。这样通过订单号进行查询可以获得分片信息从而查询 1 个分片就能得到最终的结果。
全局表
在分布式数据库中有时会有一些无法提供分片键的表但这些表又非常小一般用于保存一些全局信息平时更新也较少绝大多数场景仅用于查询操作。
例如 tpch 库中的表 nation用于存储国家信息但是在我们前面的 SQL 关联查询中又经常会使用到这张表对于这种全局表可以在每个分片中存储这样就不用跨分片地进行查询了。如下面的设计 唯一索引
最后我们来谈谈唯一索引的设计与主键一样如果只是通过数据库表本身唯一约束创建的索引则无法保证在所有分片中都是唯一的。
所以在分布式数据库中唯一索引一样要通过类似主键的 UUID 的机制实现用全局唯一去替代局部唯一但实际上即便是单机的 MySQL 数据库架构我们也推荐使用全局唯一的设计。因为你不知道什么时候你的业务就会升级到全局唯一的要求了。
总结
今天介绍了非常重要的分布式数据库索引设计内容非常干货是分布式架构设计的重中之重建议反复阅读抓住本文的重点总结来说
分布式数据库主键设计使用有序 UUID全局唯一分布式数据库唯一索引设计使用 UUID 的全局唯一设计避免局部索引导致的唯一问题分布式数据库唯一索引若不是分片键则可以在设计时保存分片信息这样查询直接路由到一个分片即可对于分布式数据库中的全局表可以采用冗余机制在每个分片上进行保存。这样能避免查询时跨分片的查询。 作者让我来搞这个bug 链接https://www.jianshu.com/p/071c4ee7a532 结语
如果这篇文章对您有所帮助或者有所启发的话求一键三连点赞、评论、收藏➕关注您的支持是我坚持写作最大的动力。