- 分库分表概念
- 电商系统下订单性能瓶颈问题
- 分库分表原则剖析&产生的问题剖析
- 电商系统亿级订单数据分库分表实战指导
分库分表概念
在数据爆炸的年代,单表数据达到千万级别,甚至过亿的量,都是很常见的情景。这时候再对数据库进行操作就是非常吃力的事情了,select个半天都出不来数据,这时候业务已经难以维系。不得已,分库分表提上日程,我们的目的很简单,减小数据库的压力,缩短表的操作时间。
1、分表
分表是指在一个或者多个数据库实例内,将一张表拆分为多张表存储。一般来说,分表是因为该表需要存储很庞大的记录数,如果将其堆积到一起,就会导致数据量过于庞大(一般 MySQL 的表是 5000 万条记录左右)引发性能瓶颈。一般分表会按照某种算法进行拆分,如交易记录,可能按年份拆分:
2、分库
分库是指将一套数据库的设计结构,部署到多个数据库实例的节点中去,在应用的时候,按照一定的方法通过多个数据库实例节点访问数据。请注意,这里的数据库实例节点是一个逻辑概念,不是一个物理概念,什么意思呢?简单地说,一个机器节点可以部署多个数据库实例节点,也可以一个机器节点只部署一个数据库实例节点,所以机器节点不一定等于数据库节点。而机器节点是物理概念,是看得到的真实的机器;数据库节点是逻辑概念,是看不到的东西。为了更好地说明分库的概念,如下图
Why分库分表:
1、前提
移动互联网时代,海量的用户每天产生海量的数量,比如:
- 用户表
- 订单表
- 交易流水表
以支付宝用户为例,8亿;微信用户更是多大10亿。订单表更夸张,比如美团外卖,每天都是几千万的订单。淘宝的历史订单总量应该是百亿,甚至是千亿级别,这些海量数据远不是一张表能Hold住的。事实上MySQL单表可以存储10亿级数据,只是这时候性能比较差,业界公认MySQL单表容量在1KW以下是最佳状态,因为这是它的BTREE索引树高在3~5之间。
既然一张表无法搞定,那么就想办法将数据放到多个地方,目前比较普遍的方案有3个:
- 分区
- 分库分表
- NoSQL/NewSQL
说明:只分库,或者只分表,或者分库分表融合方案都统一认为是分库分表方案,因为分库,或者分表只是一种特殊的分库分表而已。NoSQL比较具有代表性的是MongoDB,es。NewSQL比较具有代表性的是TiDB
2、Why Not 分区
分区原理:
分区技术与分表技术很类似,只是分区技术属于数据库内部的技术,对于开发者来说,它逻辑上仍旧是一张表,开发时不需要改变 SQL 表名。将一张表切分为多个物理区块
它的缺点很明显:很多的资源都受到单机的限制,例如连接数,网络吞吐等!虽然每个分区可以独立存储,但是分区表的总入口还是一个MySQL示例。从而导致它的并发能力非常一般,远远达不到互联网高并发的要求!
使用分区,你的业务必须具备两个特点:
- 数据不是海量(分区数有限,存储能力有限)
- 并发能力要求不高
3、Why Not NoSQL/NewSQL
目前绝大部分公司的核心数据都是:以RDBMS存储为主,NoSQL/NewSQL存储为辅!互联网公司又以MySQL为主,国企&银行等不差钱的企业以Oracle/DB2为主!NoSQL/NewSQL宣传的无论多牛逼,就现在各大公司对它的定位,都是RDBMS的补充,而不是取而代之!
4、Why 分库分表
首先目前互联网行业处理海量数据的通用方法:分库分表。
虽然大家都是采用分库分表方案来处理海量核心数据,但是还没有一个一统江湖的中间件,这里列举一些有一定知名度的分库分表中间件:
- 阿里的TDDL,DRDS和cobar
- 开源社区的Sharding-jdbc(3.x已经更名为sharding-sphere)
- 民间组织的MyCAT
- 360的Atlas
备注:sharding-jdbc的作者张亮大神原来在当当,现在在京东金融。但是sharding-jdbc的版权属于开源社区,不是公司的,也不是张亮个人的!
分库分表中间件归结类型
分库分表的中间件很多,但是可以归结为两大类型:
- CLIENT模式
- PROXY模式
1、CLIENT模式:
代表有阿里的TDDL,开源社区的sharding-jdbc(sharding-jdbc的3.x版本即sharding-sphere已经支持了proxy模式)。架构如下:
2、PROXY模式:
代表有阿里的cobar,民间组织的MyCAT。架构如下:
3、小结
从以上两种模式可以看出sharding-jdbc作为一个组件集成在应用内,而mycat则作为一个独立的应用需要单独部署。
但是,无论是CLIENT模式,还是PROXY模式。几个核心的步骤是一样的:SQL解析,重写,路由,执行,结果归并。
client模式,架构简单,性能损耗比较小,运维成本低。
mycat的单机模式无法保证可靠性,一旦宕机则服务就变得不可用,你又不得不引入HAProxy来实现他的高可用集群部署方案,为了解决HAProxy的高可用问题,有需要采用keepalived来实现。
电商系统下订单性能瓶颈问题
1、性能瓶颈的产生
1)、 原大众点评的订单单表早就已经突破200G,由于查询维度较多,即使加了两个从库,优化索引,仍然存在很多查询不理想的情况。去年大量抢购活动的开展,使数据库达到瓶颈,应用智能通过限速、异步队列等对其进行保护;业务需求层出不穷,原有的订单模型很难满足业务需求,但是基于原订单表的DDL又非常吃力,无法达到业务要求。
2)、订单表在电商项目中存储的数量是最多的,但是电商项目中使用mysql数据库较多,那么mysql的存储量最佳大概在1000w条,那么当存储量大于1000w条的时候,查询订单就会性能很差。
3)、IO瓶颈
- 磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询是会产生大量的IO,降低查询速度 ->分库操作和垂直分表操作
- 网络IO瓶颈,请求的数据太多,网络带宽不够 -> 分库操作
4)、CPU瓶颈
- SQL问题,如SQL中包含join,group by ,order by ,非索引字段条件查询等,增加CPU运算的操作 ->SQL优化,建立一个合适的索引,在业务中进行运算优化。
- 单表数据量太大,查询时扫描的行太多,SQL效率低,CPU率先出现瓶颈 -->水平分表操作
2、性能瓶颈的破除
随着以上问题越来越突出,订单数据库的切分就愈发急迫了。本次切分,我们的目标是未来几年内不需要担心订单容量的问题。
使用分库分表和SQL优化来解决这个订单性能瓶颈问题。
分库分表原则剖析&产生的问题剖析
1、分库分表原则
1)、能不分就不分
MySQL是关系型数据库,数据库表之间的关系从一定的角度上映射了业务逻辑。任何分库分表的行为都会在某种程度上提升业务逻辑的复杂度,数据库除了承载数据的存储和访问外,协助业务更好的实现需求和逻辑也是其重要工作之一。分库分表会带来数据的合并,查询或者更新条件的分离,事务的分离等等多种后果,业务实现的复杂程度往往会翻倍或者指数级上升。所以,在分库分表之前,不要为分而分,去做其他力所能及的事情,比如升级硬件,升级网络,升级数据库库版本,读写分离,负载均衡等。所有分库分表的前提是,这些都已经尽力了。
2)、数据量太大,正常的运维不满足正常业务访问
对数据库的备份。如果单表或者单个实例太大,在做备份的时候需要大量的磁盘IO或者网络IO资源。例如1T的数据,网络传输占用50MB的时候,需要20000秒才能传输完毕,在此整个过程中的维护风险都是高于平时的。
对数据表的修改。如果某个表过大,对此表做DDL的时候,MySQL会锁住全表,这个时间可能很长,在这段时间业务不能访问此表,影响甚大。
整个表热点,数据访问和更新频繁,经常有锁等待,你又没有能力去修改源码,降低锁的粒度,那么只会把其中的数据物理拆开,用空间换时间,变相降低访问压力。
3)、表设计不合理,需要对某些字段垂直拆分
举一个例子,如果一个用户表,在最初设计表的时候是这样的:
users表
这种设计是很常见的,但是:
情况:1:
你的业务中如果用户数从100W增长到10亿。你为了统计活跃用户,在每个人登录的时候都会记录一下他的最近登录时间。并且用户活跃的很,不断的去update这个login_time字段值,那么这个表就会压力很大。站在业务的角度上,最好的办法就是先把last_login_time字段拆分出去,我们暂且叫他user_time。这样做的好处是业务的代码只有在用到这个字段的时候修改一下就行了。如果不这么做,直接把users表水平切分了,那么所有访问users表的地方都要进行修改。
情况2:
personal_info这个字段本来没啥用,就是让用户注册的时候填一些个人爱好而已,基本不查询。一开始的时候有没有无所谓,但是后来发现两个问题:一,这个字段占用了大量的空间,因为是text类型,有很多人喜欢长篇大论地介绍自己。更糟糕的是二,不知道哪天哪个产品经理心血来潮,说允许个人信息公开吧,以方便让大家更好的相互了解。那么在所有人猎奇窥私心理的影响下,对此字段的访问大幅度增加。数据库压力瞬间扛不住了,这个时候,只好考虑对这个表进行垂直拆分。
4)、某些数据表出现了无穷增长
在目前项目中,各种的评论,消息,日志记录。这个增长不是跟人口成比例的,而是不可控的,例如微博的feed的广播,我发一条消息,会扩散给很多很多人。虽然主体可能只存一份,但不排除一些索引或者路由有这种存储需求。这个时候,增加存储,提升机器配置已经苍白无力了,水平切分是最佳实践。
拆分的标准很多,按用户的,按时间的,按用途的等等方式进行拆分。
5)、安全性和可用性的考虑
这个很容易理解,鸡蛋不要放在一个篮子里,我不希望我的数据库出问题,但我希望在出问题的时候不要影响到100%的用户,这个影响的比例越少越好,那么,水平切分可以解决这个问题,把用户,库存,订单等等本来同统一的资源切分掉,每个小的数据库实例承担一小部分业务,这样整体的可用性就会提升。这对Qunar(去哪儿)这样的业务还是比较合适的,人与人之间,某些库存与库存之间,关联不太大,可以做一些这样的切分。
6)、业务耦合性考虑
这个跟上面有点类似,主要是站在业务的层面上,我们的火车票业务和烤羊腿业务是完全无关的业务,虽然每个业务的数据量可能不太大,放在一个MySQL实例中完全没问题,但是很可能烤羊腿业务的DBA或者开发人员水平很差,动不动给你出一些幺蛾子,直接把数据库搞挂。这个时候,火车票业务的人员虽然技术很优秀,工作也很努力,照样被老板打屁股。解决的办法很简单:惹不起,躲得起。
2、分库分表架构方案
1)、垂直切分
垂直切分常见有垂直分库和垂直分表两种。
A.垂直分库:
是根据数据库里面的数据库表的相关性进行拆分,比如:一个数据库里面既存在用户数据,又存在订单数据,那么垂直拆分可以吧用户数据放在用户库,把订单数据放到订单库。另外在“微服务”盛行的今天已经非常普及,按照业务模块来划分不同的数据库,也是一种垂直拆分,而不是像早期一样将所有的数据表都放在同一个数据库中。如下图
以表为依据,按照业务归属不同,将不同的表拆分到不同的库中
注意事项:
1、每个库的结构都一样
2、每个库的数据也不一样,没有交集
3、所有库的并集是全部数据
使用垂直分库的场景:
当系统绝对并发量上来了,并且可以抽象出单独的业务模块的情况下使用垂直分库方案。
B.垂直分表
在日常开发和设计中比较常见,通俗的说法叫做“大表拆小表”。拆分是基于关系型数据库中的“列”(字段)进行的。通常情况,某个表中的字段比较多,可以新建立一张"扩展表",将不经常使用或者长度较大的字段拆分出去放到“扩展表”中,如下图所示:
以字段为依据,按照字段的活跃性,将表中字段拆到不同的表中(主表和扩展表)。
说明:拆分字段的操作建议在数据库设计阶段就做好。如果是在发展过程中拆分,则需要改写以前的查询语句,会额外带来一定的成本和风险,建议谨慎。
注意事项:
1、每个表的结构不一样
2、每个表的数据也不一样,一般来说,每个表的字段只有有一列交集,一般是主键,用于关联数据
3、所有表的并集是全部数据
使用垂直分表的场景:
当系统绝对并发量没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需要的存储空间较大,以致于数据库缓存的数据行减少,查询时回去读磁盘数据产生大量随机读IO,产生IO瓶颈的时候使用垂直分表。
C.垂直拆分的优点和缺点:
优点:
- 拆分后业务清晰,拆分规则明确
- 系统之间进行整合或扩展很容易
- 按照成本,应用的等级,应用的类型等将表放在不同的机器上,便于管理便于实现动静分离,冷热分离的数据库表的设计模式
- 数据维护简单。
缺点:
- 主键出现冗余,需要管理冗余列。
- 会引起表连接JOIN操作(增加CPU开销)可以通过在业务服务器上进行join来减少数据库压力。
- 依然存在单表数据量过大的问题(需要水平拆分)。
- 事务处理复杂。
2)、水平切分
水平切分是通过某种策略将数据分片来存储,分为水平分表和水平分库两部分,每片数据会分散到不同的MySQL表或者库中,达到分布式的效果,能够支持非常大的数据量。
A.水平分库
以字段为依据,按照一定策略(hash、range等),将一个库中的数据拆分到多个库中。
注意事项:
1、每个库的结构都一样
2、每个库中的数据不一样,没有交集
3、所有库的数据并集是全部数据
使用水平分库的场景:
当系统绝对并发量上来了,分表难以从根本上解决问题,并且还没有明显的业务归属进行抽取业务的情况下使用水平分库方案。
B.水平分表
以字段为依据,按照一定策略(hash,range等),将一个表中的数据拆分到多个表中。
注意事项:
1、每个表的结构都一样
2、每个表的数据不一样,没有交集,所有表的并集是全部数据
使用水平分表的场景:
当系统绝对并发量没有上来,只是单表的数据量太多,影响SQL效率,加重了CPU负担,以致于成为瓶颈的情况下使用水平分表方案。
C.水平拆分的优点和缺点:
优点:
- 单库单表的数据保持在一定的量级,有助于性能的提高
- 切分的表结构相同,应用层改造较少,只需要增加路由规则即可
- 提高了系统的稳定性和负载能力
缺点:
- 切分后,数据是分散的,很难利用数据库的Join操作,跨库Join性能较差
- 拆分规则难以抽象
- 分片事务的一致性难以解决
- 数据多次拓展难度跟维护量极
3)、小结
垂直分库和水平分库的区别
- 垂直分库是按照业务模块的不同进行拆分
- 水平分库是按照一定的策略进行拆分,达到把一个库中的数据进行拆分到不同的数据库中。
- 垂直分表和水平分表的区别垂直分表是按照表中的冷热数据(活跃数据)进行拆分的,并且拆分完了之后,一般是分主表和拓展表,那么两张表的结构不一样,两张表的关联靠id来支持。
- 水平分表是按照一定的策略进行拆分,目的是把单表的庞大的数量按照一定的策略分摊到拆分之后的小表中,拆分之后,每张表的结构是一样的,但是数据不一样。
3、分库分表产生的问题剖析
分库分表能有效的缓解单机单库和单表带来的性能瓶颈和压力,突破网络IO、硬件资源、连接数的瓶颈,同时也带来一些问题,问题如下:
1)、事务一致性问题
分布式事务:
当更新内容同时存在于不同库找那个,不可避免会带来跨库事务问题。跨分片事务也是分布式事务,没有简单的方案,一般可使用“XA 协议”和“两阶段提交”处理。
分布式事务能最大限度保证了数据库操作的原子性。但在提交事务时需要协调多个节点,推后了提交事务的时间点,延长了事务的执行时间,导致事务在访问共享资源时发生冲突或死锁的概率增高。
随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面上水平扩展的枷锁。
最终一致性:
对于那些性能要求很高,但对一致性要求不高的系统,往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。
与事务在执行中发生错误立刻回滚的方式不同,事务补偿是一种事后检查补救的措施,一些常见的实现方法有:对数据进行对账检查,基于日志进行对比,定期同标准数据来源进行同步等。
解决办法:
使用分布式事务,比如阿里的Seata分布式事务,XA两阶段事务,saga柔性事务。
2)、跨节点关联查询Join问题
切分之前,系统中很多列表和详情表的数据可以通过 Join 来完成,但是切分之后,数据可能分布在不同的节点上,此时 Join 带来的问题就比较麻烦了,考虑到性能,尽量避免使用 Join 查询。
解决的办法:
全局表:
全局表,也可看做“数据字典表”,就是系统中所有模块都可能依赖的一些表,为了避免库 Join 查询,可以将这类表在每个数据库中都保存一份。这些数据通常很少修改,所以不必担心一致性的问题。
字段冗余:
一种典型的反范式设计,利用空间换时间,为了性能而避免 Join 查询。
例如,订单表在保存 userId 的时候,也将 userName 也冗余的保存一份,这样查询订单详情表就可以查到用户名 userName,就不用查询买家 user 表了。
但这种方法适用场景也有限,比较适用依赖字段比较少的情况,而冗余字段的一致性也较难保证。
数据组装(常用):
在系统 Service 业务层面,分两次查询,第一次查询的结果集找出关联的数据 id,然后根据 id 发起器第二次请求得到关联数据,最后将获得的结果进行字段组装。这是比较常用的方法。
ER分片:、
关系型数据库中,如果已经确定了表之间的关联关系(如订单表和订单详情表),并且将那些存在关联关系的表记录存放在同一个分片上,那么就能较好地避免跨分片 Join 的问题。
可以在一个分片内进行 Join,在 1:1 或 1:n 的情况下,通常按照主表的 ID 进行主键切分。
3)、跨节点分页、排序、函数问题
跨节点多库进行查询时,会出现 limit 分页、order by 排序等问题。
分页需要按照指定字段进行排序,当排序字段就是分片字段时,通过分片规则就比较容易定位到指定的分片;当排序字段非分片字段时,就变得比较复杂。
需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序
最终返回给用户如下图:
上图只是取第一页的数据,对性能影响还不是很大。但是如果取得页数很大,情况就变得复杂的多。
因为各分片节点中的数据可能是随机的,为了排序的准确性,需要将所有节点的前N页数据都排序好做合并,最后再进行整体排序,这样的操作很耗费 CPU 和内存资源,所以页数越大,系统性能就会越差。
在使用 Max、Min、Sum、Count 之类的函数进行计算的时候,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总再次计算。
4)、全局主键避重问题
在分库分表环境中,由于表中数据同时存在不同数据库中,主键值平时使用的自增长将无用武之地,某个分区数据库自生成 ID 无法保证全局唯一。
因此需要单独设计全局主键,避免跨库主键重复问题。这里有一些策略:
- Twitter的Snowflake(又名“雪花算法”)
- UUID/GUID(一般应用程序和数据库均支持)
- MongoDB ObjectID(类似UUID的方式)
- Ticket Server(数据库生存方式,Flickr采用的就是这种方式)
5)、数据迁移、扩容问题
当业务高速发展、面临性能和存储瓶颈时,才会考虑分片设计,此时就不可避免的需要考虑历史数据的迁移问题。
一般做法是先读出历史数据,然后按照指定的分片规则再将数据写入到各分片节点中。
此外还需要根据当前的数据量个 QPS,以及业务发展速度,进行容量规划,推算出大概需要多少分片(一般建议单个分片的单表数据量不超过 1000W)。
电商系统亿级订单数据分库分表实战指导
Sharding-JDBC中间件
1)、sharding-jdbc是什么?
Sharding-JDBC是分布式数据中间件Sharding-Sphere中的重要组成部分,官方的介绍如下:
官网地址:
https://shardingsphere.apache.org/
Sharding-Sphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding
JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。
定位为轻量级Java框架,在Java的JDBC层提供的额外服务,它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
- 适用于任何基于JDBC的ORM框架,如JPA,Hibernate,Mybatis,Spring JDBC Template或直接使用JDBC。
- 支持任何第三方的数据库连接池,如DBCP,C3P0,Druid,HikariCP等。
- 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer以及任何遵循SQL92标准的数据库。
2)、sharding-jdbc能做什么?
Sharding-JDBC的核心功能为数据分片和读写分离,通过sharding-JDBC,应用可以透明的使用jdbc访问已经分库,读写分离的多个数据源,而不用关心数据源的数量以及数据如何分布。
A.sharding-jdbc核心概念:
- 逻辑表(LogicTable):具有相同逻辑和数据结构的水平分片数据库(表)。例如订单数据根据主键最后一个数字划分为10个表,他们是从tb_order_0到tb_order_9,其逻辑名称为tb_order。
- 实际表(ActualTable):真正存在于分片数据库的物理表,即tb_order_0到tb_order_9。
- 数据节点(DataNode):数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.tb_order_0。
- 动态表(DynamicTable):逻辑表和物理表不一定需要在配置规则中静态配置。如,按照日期分片的场景,物理表的名称随时间的推移会产生变化。 比如: tb_order_0一定是静态表,tb_order_$有可能是tb_order_1,也有可能是tb_order_2。
- 绑定表(BindingTable):指分片规则一致的主表和子表。例如:tb_order表和tb_order_item表,均按照order_id分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。
举例说明,如果SQL为:
SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE
o.order_id in (10, 11)
在不配置绑定表关系时,假设分片键order_id 将数值10路由至第0片,将数值11路由至第1片,那么路由后的SQL应该为4条,他们会出现笛卡尔积现象:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id
WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id
WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id
WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id
WHERE o.order_id in (10, 11);
在配置绑定表配置后,路由的SQL应该为2条:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id
WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id
WHERE o.order_id in (10, 11);
其中t_order在FROM的最左侧,ShardingSphere将会以它作为整个绑定表的主表。 所有路由计算将会只使用主表的策略,那么t_order_item表的分片计算将会使用t_order的条件。故绑定表之间的分区键要完全相同。
B.sharding-jdbc分片键,算法,策略:
- 片键:用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。
- 分片算法:通过分片算法将数据分片,支持通过 = 、 >= 、 <= 、 > 、 < 、 BETWEEN 和 IN 分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。
目前提供4种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。
- 精确分片算法
对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。
- 范围分片算法
对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用。
- 复合分片算法
对应
ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。
- Hint分片算法
对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。
分片策略:包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。
- 标准分片策略
对应StandardShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。
- 复合分片策略
对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。行表达式分片策略对应InlineShardingStrategy
- 使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0 到 t_user_7 。
C.sharding-jdbc快速入门:
1、需求说明:
使用sharding-jdbc完成对订单表的水平分表,通过快速入门的开发,了解sharding-jdbc使用方法
人工创建两张表,t_order_1和t_order_2,这两张表是订单表拆分后的表,通过sharding-jdbc向订单表插入数据,按照一定的分片规则,主键为偶数的进入t_order_1,另一部分数据进入t_order_2,通过sharding-jdbc查询数据,根据SQL语句的内容从t_order_1或t_order_2查询数据。
2、环境搭建:
1、创建订单数据库order_db
CREATE DATABASE order_db CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
2、在order_db中创建t_order_1、t_order_2表
DROP TABLE IF EXISTS t_order_1;
CREATE TABLE t_order_1(
`order_id` BIGINT(20) NOT NULL COMMENT '订单id',
`price` DECIMAL(10,2) NOT NULL COMMENT '订单价格',
`user_id` BIGINT(20) NOT NULL COMMENT '下单用户id',
`status` VARCHAR(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT
NULL COMMENT '订单状态',
PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT
= Dynamic;
DROP TABLE IF EXISTS t_order_2;
CREATE TABLE t_order_2(
`order_id` BIGINT(20) NOT NULL COMMENT '订单id',
`price` DECIMAL(10,2) NOT NULL COMMENT '订单价格',
`user_id` BIGINT(20) NOT NULL COMMENT '下单用户id',
`status` VARCHAR(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT
NULL COMMENT '订单状态',
PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT
= Dynamic;
3、引入maven依赖坐标pom.xml
3、配置分片规则:
分片规则配置是sharding-jdbc进行对分库分表操作的重要依据,配置内容包括:数据源、主键生成策略、分片策 略等。 在application.properties中配置
4、测试新增订单:
1、创建订单实体
2、创建mapper接口
3、测试新增订单
4、控制台打印sql
6、小结:
通过日志分析,Sharding-JDBC在拿到用户要执行的sql之后干了哪些事儿:
- 解析sql,获取分片键值,在本例中是order_id。
- Sharding-JDBC通过规则配置 t_order_$->{order_id % 2 + 1},知道了当order_id为偶数时,应该往t_order_1表插数据,为奇数时,往t_order_2插数据。
- 于是Sharding-JDBC根据order_id的值改写sql语句,改写后的SQL语句是真实所要执行的SQL语句。
- 执行改写后的真实sql语句。
- 将所有真正执行sql的结果进行汇总合并,返回。
D.sharding-jdbc执行原理: