拓展阅读
分表分库
概念
常见分表分库方式
按照功能分库
按照功能进行分库。常见的分成 6 大库:
-
1 用户类库:用于保存了用户的相关信息。
-
2 业务类库:用于保存主要业务的信息。比如主要业务是笑话,用这个库保存笑话业务。
-
3 内存类库:主要用Mysql的内存引擎。前台的数据从内存库中查找,速度快。
-
4 图片类库:主要保存图片的索引以及关联。
-
5 日志类库:记录点击,刷新,登录等日志信息。
-
6 统计类库:对业务的统计,比如点击量, 刷新量等等。
如果业务遍布全国
按照功能分库库,每一个城市复制一份一模一样的库,只是库后缀都是城市名称。
比如 db_log_click_bj, db_log_click_tj, db_log_click_sh;
分表
水平分割
解决表行数过大问题
- 按照用户或业务的编号分表
对与用户或业务可以按照 编号%n ,进行分成 n 表。
例如:笑话表。
tb_joke_01, tb_joke_02, tb_joke_03, tb_joke_04 …
- 按照日期分表
对于日志或统计类等的表。可以按照年,月,日,周分表。
例如点击量统计。
tb_click_stat_201601,tb_click_stat_201602,tb_click_stat_201603
常见算法
- 范围法
以用户中心的业务主键uid为划分依据,将数据水平切分到两个数据库实例上去:
user-db1:存储0到1千万的uid数据
user-db2:存储0到2千万的uid数据
- 哈希法
也是以用户中心的业务主键uid为划分依据,将数据水平切分到两个数据库实例上去:
user-db1:存储uid取模得1的uid数据
user-db2:存储uid取模得0的uid数据
这两种方法在互联网都有使用,其中哈希法使用较为广泛。
特点
水平切分是指,以某个字段为依据(例如uid),按照一定规则(例如取模),将一个库(表)上的数据拆分到多个库(表)上,以降低单库(表)大小,达到提升性能的目的的方法。
水平切分后,各个库(表)的特点是:
(1)每个库(表)的结构都一样
(2)每个库(表)的数据都不一样,没有交集
(3)所有库(表)的并集是全量数据
优点
-
解决单表单库大数据量和高热点访问性能遇到瓶颈的问题;
-
应用程序端整体架构改动相对较少。
-
事务处理相对简单。
-
只要切分规则能够定义好,基本上较难遇到扩展性限制。
缺点
-
拆分规则相对更复杂,很难抽象出一个能够满足整个数据库的切分规则。
-
后期数据的维护难度有所增加,人为手工定位数据更困难。
-
产品逻辑将变复杂。比如按年来进行历史数据归档拆分,这个时候在页面设计上就需要约束用户必须要先选择年,然后才能进行查询。
垂直分割
解决列过长问题。
1)经常组合查询的列放在一张表中。常用字段的表可以考虑用 Memory 引擎。
2)把不常用的字段单独放在一张表。
3)把 text,blob 等大字段拆分出来放在附表中。
特点
垂直拆分是指,将一个属性较多,一行数据较大的表,将不同的属性拆分到不同的表中,以降低单库(表)大小,达到提升性能的目的的方法。
垂直切分后,各个库(表)的特点是:
(1)每个库(表)的结构都不一样
(2)一般来说,每个库(表)的属性至少有一列交集,一般是主键
(3)所有库(表)的并集是全量数据
依据
当一个表属性很多时,如何来进行垂直拆分呢?如果没有特殊情况,拆分依据主要有几点:
(1)将长度较短,访问频率较高的属性尽量放在一个表里,这个表暂且称为主表
(2)将字段较长,访问频率较低的属性尽量放在一个表里,这个表暂且称为扩展表
如果1和2都满足,还可以考虑第三点:
(3)经常一起访问的属性,也可以放在一个表里
优先考虑1和2,第3点不是必须。另,如果实在属性过多,主表和扩展表都可以有多个。
一般来说,数据量并发量比较大时,数据库的上层都会有一个服务层。
需要注意的是,当应用方需要同时访问主表和扩展表中的属性时,服务层不要使用join来连表访问,而应该分两次进行查询:
原因是,大数据高并发互联网场景下,一般来说,吞吐量和扩展性是主要矛盾:
(1)join 更消损耗数据库性能
(2)join 会让 base 表和 ext 表耦合在一起(必须在一个数据库实例上),不利于数据量大时拆分到不同的数据库实例上(机器上)。毕竟减少数据量,提升性能才是垂直拆分的初衷。
+--------------+
| user-service |
+--------------+
^
|
+--------------------+
| |
| |
+--------------+ +----------+
| user-base | | user-ext |
+--------------+ +----------+
为什么能优化性能
为何要将字段短,访问频率高的属性放到一个表内?为何这么垂直拆分可以提升性能?因为:
(1)数据库有自己的内存 buffer,会将磁盘上的数据 load 到内存 buffer 里(暂且理解为进程内缓存吧)
(2)内存 buffer 缓存数据是以 row 为单位的
(3)在内存有限的情况下,在数据库内存 buffer 里缓存短 row,就能缓存更多的数据
(4)在数据库内存 buffer 里缓存访问频率高的 row,就能提升缓存命中率,减少磁盘的访问
- 例子
举个例子就很好理解了:
假设数据库内存buffer为1G,未拆分的user表1行数据大小为1k,那么只能缓存100w行数据。
如果垂直拆分成 user_base 和 user_ext,其中:
(1)user_base 访问频率高(例如uid, name, passwd, 以及一些flag等),一行大小为0.1k
(2)user_ext 访问频率低(例如签名, 个人介绍等),一行大小为0.9k
那边内存buffer就就能缓存近乎1000w行user_base的记录,访问磁盘的概率会大大降低,数据库访问的时延会大大降低,吞吐量会大大增加。
优点
-
库表职责单一,复杂度降低,易于维护。
-
单库或单表压力降低,相互之间的影响也会降低。
缺点
-
部分表关联无法在数据库级别完成,需要在程序中完成。
-
单表大数据量仍然存在性能瓶颈。
-
单表或单库高热点访问依旧对 DB 压力非常大。
-
事务处理相对更为复杂。需要分布式事务的介入。
-
拆分达到一定程度之后,扩展性会遇到限制。
一些问题
需要考虑的点
-
容量规划:现有的数据量有多大,每天或者每月增长量是多少。现在需要分多少个库表,分完能够支撑多长时间。
-
分库分表策略确定:数据如何分布均匀,分多少库 分多少表。按范围分还是年月分还是HASH取模啥的。
-
扩容等问题。一旦现有容量到达极限,如何进行扩容?扩容过程中数据迁移量有多少?
-
如何进行历史数据迁移。
数据迁移问题
-
双写 以老库为主。读操作还是读老库老表,写操作是双写到新老表。
-
历史数据迁移 dts + 新数据对账校验(job) + 历史数据校验。
-
切读:读写以新表为主,新表成功就成功了。
-
观察几天 下掉写老库操作。
带来的问题
事务问题
分布式事务 补偿 或者最终一致性 最大努力送达型
跨节点 join 的问题:
-
换条技术栈:使用 ES 或者其他 NOSQL 数据库。
-
分两次查询实现。在第一次查询的结果集中找出关联数据的id,然后根据这些id发起第二次请求得到关联数据。
跨表或跨库的 count、order by、group by 以及聚合函数问题。
这些是一类问题,因为它们都需要基于全部数据集合进行计算。
解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
非分表字段查询问题:再加一张中间表;换条技术栈。
跨分片的排序分页
一般来讲,分页时需要按照指定字段进行排序。
当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。
为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最终再返回给用户。
如下图所示:
上面图中所描述的只是最简单的一种情况(取第一页数据),看起来对性能的影响并不大。
但是,如果想取出第 10 页数据,情况又将变得复杂很多,如下图所示:
技术栈
HybridDB
HybridDB 是同时支持海量数据在线事务(OLTP)和在线分析(OLAP)的HTAP(Hybrid Transaction/Analytical Processing)关系型数据库。
HybridDB for MySQL采用一份数据存储来进行OLTP和OLAP处理,解决了以往需要把一份数据进行多次复制来分别进行业务交易和数据分析的问题,极大的降低了数据存储的成本。
HybridDB for MySQL 免去了以往在线数据库(Operational Database)和离线数据仓库(Data Warehouse)之间的海量数据加载过程,极大的缩短了数据分析的延迟,使得实时分析决策系统成为可能。
HybridDB for MySQL 最大程度的兼容MySQL的语法及函数,并且增加了对部分Oracle函数的支持,从而降低了用户的开发、迁移和维护成本。
sharding-jdbc
教训
如果不是万不得已,不要分表。
拓展阅读
参考资料
- other
https://www.jianshu.com/p/10c6fa27f889
https://dbaplus.cn/news-141-2017-1.html
- 中间件
https://juejin.im/entry/591968f6a0bb9f005ff7af23
https://github.com/yuanwhy/simple-sharding