为什么需要分库分表?是因为MySQL等数据库单库单表能支撑的系统并发量和数据存储量存在瓶颈。
中国有超过 10 亿的网民,单日交易量超过百万、千万单的交易场景越来越多。按照一行数据 1K ,每日订单量 500 万计算,一年需要的数据存储为 1700G,行数1.7亿。
显然此种情况MySQL 单库单表难以保证查询写入性能。一张表一年的数据量恐怖如斯,交易场景成百上千张表,年复一年,所带来的存储和并发量压力何其巨大呢?业务牵引技术的发展,分库分表技术应运而生。
目前分库分表技术似乎成为行业标准,站在鄙视链上游。不分库分表的系统似乎都是毫无亮点的垃圾系统,然而真的如此吗?
分库分表技术是完美的解决方案吗?它有哪些不足之处?
为什么单库单表存在系统瓶颈,就选择分库分表呢?有其他更好的方案吗?
接下来我将分享自己在电商交易场景实际遇到的痛点:
一、分库分表难以解决分布式事务问题
一般情况下通过选择合适的分片属性,可以保证业务在一个数据库内完成事务操作。
如用户领券场景,用户一次领取 3 张优惠券,系统写入 3 条优惠券记录和一条领券流水,使用 UserId 进行分库分表,可以保证同一个用户的 4 条记录路由到同一数据库,可使用 MySQL 本地事务即可保证数据一致性。
然而**当同一个事务中多张表的分片属性不同时,难以保证这些表在同一个数据库内完成事务操作,势必会出现难以解决的分布式事务难题**。
如优惠券存在库存,在发券时需同时扣减券库存,库存粒度为券模版ID,用户可同时领取多个券模版的券。如何保证库存扣减、用户发券、领取流水等在同一个数据库内完成呢?难以实现
优惠券和领券流水,基于 UserId 拆分数据库,但库存表无用户属性,只能使用券模版 ID分库。然而不同的分片属性,无法保证在同一个数据库完成事务操作,难以保证数据强一致性。
因此 当同一个事务中多张表的分片属性不同时,难以保证这些表在同一个数据库内完成事务操作,势必会出现难以解决的分布式事务难题。即便业务侧使用复杂的业务架构也难以实现强一致性,作出妥协后,可实现最终一致性。
分库分表技术把数据库解决不了的问题推到业务侧,“胁迫” 业务侧使用复杂方案参与解决,要求业务侧在数据一致性上作出妥协
二、分库分表难以实现非分片属性的索引查询能力
电商交易场景一般使用 UserId 作为分片属性,联合索引的前缀都是UserId,但这并不绝对,交易场景中存在品牌、门店、司机、配送等其他维度。
如订单表使用UserId 作为分片属性进行分库分表,查询商家在最近 1 小时的订单如何实现呢?由于使用 UserId 分库分表,当使用商家 ID 查询时,需要查询所有的分库分表,这显然不现实。
业务侧有几种实现方案,1)使用商家 ID分库分表,异构另一份 MySQL存储 2)基于 ElasticSearch,异构另一种存储支撑其他维度的检索场景。
无论哪一种方案都会导致,数据一致性降低,但系统复杂度增加。
查询商家最近1 小时的订单,业务上如此简单清晰的需求,在分库分表后,实现方案竟如此复杂。此外还要求业务在数据准确性上作出妥协。
正是因为分库分表技术的先天不足,所以极大地增加了业务系统架构的复杂性。(甚至很多人以架构复杂为荣,错误的认为系统越复杂越能体现架构能力)
此外还有其他更加 tricky的方案,如为了实现订单 Id 查询,在生成的订单Id中存放UserId后四位(大概意思),用于定位订单在哪个分片。
三、分库分表导致的全局主键问题
MySQL 主键支持自增 ID,但是这一特性在分库分表后沦为鸡肋功能,系统需要分布式方式的 ID 生成器。
如优惠券系统在分库分表后,优惠券 ID 需要借助分布式ID生成器生成全局唯一 ID。常见的实现方式包括 UUID、雪花算法、美团 Leaf、百度 UidGenerator等。除维护业务系统外,还需要维护其他纯技术类系统。
毫无疑问,这再次增加架构的复杂性。
四、分库分表难以建立全局唯一键约束
除全局主键问题,全局唯一键也难以实现。当全局唯一键的前缀是非分片属性时,难以实现全局唯一键。
例如订单在履约完成后,会生成一笔履约单。系统明确一笔订单只能有一笔履约单,因此履约单上的订单 ID 字段应该增加唯一键约束。然而履约单在基于 UserID 分库分表后,OrderId建立的唯一键约束只在本表内唯一,不能保证在所有的分片内实现全局唯一。
因此 分库分表后,数据库无法对 OrderId等非分片属性建立全局的唯一性约束。
除以上具体场景外,分库分表面临的难题依然有很多,如扩容难问题、存储成本高、运维成本高、高可用难等等问题。
五、分库分表需要持续造轮子
选择分库分表后,需要业务系统开发和接入很多轮子,包括
ShardingSphere、mycat等方便客户端接入分库分表。如果使用 ShardingSphere需考虑多种语言多套轮子,使用 Mycat代理层方案又会面临性能风险。
分布式 ID 生成器生成全局 ID
为了支持非分片查询,需要 DTS 消费binlog,异构存储For 查询。
使用 ElasticSearch用于其他维度检索。
分库分表管理后台支持查询数据、修改数据
分库分表管理工具支持高效建表、修改表、加索引等 DDL 操作。(1000 张表后,手动建表不现实)
……
层出不穷的问题,需要层出不穷的轮子,有些轮子需要自己造,如分库分表管理工具等。
不禁要问,互联网大厂能承受如此复杂架构方案,所有的公司都能承受吗?导致架构如此复杂的根源是什么呢?分库分表方案上的先天不足
六、分库分表后面临老大难的扩容问题
在系统建设之初,一个合格的架构师必须考虑到:未来数据量庞大后的存储扩容问题。这往往让人很难抉择,因为要权衡当下的硬件成本和未来扩容成本。
使用分库分表后,扩容非常困难。如8 个数据库,想继续拆分为 16 个数据库,一定会对业务造成影响,停机迁移是难以避免的问题,需要极多的运维工具保障扩容过程的安全性和快速性,做到对业务影响程度最低。因此扩容成本和风险都极高。
如果在系统建设初期就拆分为 16 个库,又会面临硬件和运维成本过高的问题。如果每一个业务在系统建设初期都如此铺张浪费,那么公司的硬件成本将极高。
七、大量分库分表导致运维成本激增
仅一个业务团队就16 个数据库,公司那么多业务将会有多么庞大规模的数据库实例。有 DBA 曾分享经历,他一天新部署了120 套 mysql实例,可想而知,DBA 们面临了多么庞大的运维压力。
研发视角和 DBA视角不同,结论不同。业务研发更多考虑容量不足和并发度不足风险,会倾向于设置较大的分库分表规模,而 DBA 会考虑运维成本、硬件成本,希望数据库规模在可控范围内。两者站在不同的视角,各有理由,这是一个矛盾。
我相信大多数业务研发不清楚,自家公司MySQL单库单实例的最高并发度,最高容量,在设置分片规模时往往是拍脑袋决定。只要研发定的分片方案不离谱,往往 DBA也就接受了。
这些决策中,往往还掺杂历史问题。如团队已经有了 10 个库,无论业务规模如何,新表默认拆分10 个库,而不会考虑是否真的需要 10 个库。
八、为什么业务系统要替数据库负重前行?
分库分表技术把数据库解决不了的问题推到业务侧,“胁迫” 业务侧使用复杂方案参与解决,要求业务侧在数据一致性上作出妥协。
为什么单库单表存在系统瓶颈,就选择分库分表呢?有其他更好的方案吗?分布式数据库。
分布式数据库继承了传统单机数据库的核心特性,同时还拥有分布式系统的处理能力。虽然起步较晚,可以预见的是它将是数据库下一个发展方向。