订单数据主要由三张数据库表组成,主订单表对应的就是用户的一个订单,每提交一次都会生成一个主订单表的数据。在有些情况下,用户可能在一个订单中选择不同卖家的商品,而每个卖家又会按照该订单中是自己提供的商品计算相关的商品优惠(比如满88元免快递费)以及安排相关的物流配送,所以会出现子订单的概念,即一个主订单会由多个子订单组成,而真正对应到具体每个商品的订单信息,则是保存在订单详情表中。
图5-5 订单相关数据表结构示意
如果一个电商平台的业务发展健康的话,订单数据是比较容易出现因为单个数据库表中的数据太大而造成性能的瓶颈,所以需要对它进行数据库的拆分。此时从理论上对订单拆分是可以由两个维度进行的,一个维度是通过订单ID(一般为自增ID)取模的方式,即以订单ID为分库分表键;一个是通过买家用户ID的维度进行哈希取模,即以买家用户ID为分库分表键。
两种方案做一下对比:
如果是按照订单ID取模的方式,比如按64取模,则可以保证主订单数据以及相关的子订单、订单详情数据平均落入到后端的64个数据库中,原则上很好地满足了数据尽可能平均拆分的原则。
通过采用买家用户ID哈希取模的方式,比如也是按64取模,技术上则也能保证订单数据拆分到后端的64个数据库中,但这里就会出现一个业务场景中带来的一个问题,就是如果有些卖家是交易量非常大的(这样的群体不在少数),那这些卖家产生的订单数据量(特别是订单详情表的数据量)会比其他卖家要多出不少,也就是会出现数据不平均的现象,最终导致这些卖家的订单数据所在的数据库会相对其他数据库提早进入到数据归档(为了避免在线交易数据库的数据的增大带来数据库性能问题,淘宝将3个月内的订单数据保存进在线交易数据库中,超过3个月的订单会归档到后端专门的归档数据库)。
所以从对“数据尽可能平均拆分”这条原则来看,按照订单ID取模的方式看起来是更能保证订单数据进行平均拆分,但我们暂且不要这么快下结论,让我们继续从下面几条原则和最佳实践角度多思考不同的拆分维度带来的优缺点。
3.尽量减少事务边界
不管是TDDL平台还是DRDS,采用分库分表的方式将业务数据拆分后,如果每一条SQL语句中都能带有分库分表键,如图5-6所示是以自增的订单ID以8取模,将订单平均分布到8个数据库的订单表中,通过分布式服务层在对于SQL解析后都能精准地将这条SQL语句推送到该数据所在的数据库上执行,数据库将执行的结果再返回给分布式服务层,分布式服务层再将结果返回给应用,整个数据库访问的过程跟之前的单机数据库操作没有任何差别。这个是在数据进行了分库分表拆分后,SQL语句执行效率最高的方式。
图5-6DRDS对带分库分表键的SQL请求处理
但不是所有的业务场景在进行数据库访问时每次都能带分库分表键的。比如在买家中心的界面中,要显示买家test1过去三个月的订单列表信息,因为该买家test1的订单按订单ID取模的方式分布到了不同的数据库中,此时SQL语句中就没有了分库分表键值,则出现了如图5-7所示的情况,分布式数据层会将获取test1订单的SQL语句推送到后端所有数据库中执行,然后将后端数据库返回的结果在分布式数据层进行聚合后再返回给前端应用。
图5-7DRDS对不带分库分表键的SQL请求进行全表扫描处理