当我们业务数据库表中的数据越来越多,如果你也和我遇到了以下类似场景,那让我们一起来解决这个问题:
我们可以从表容量/磁盘空间/实例容量三方面评估数据体量,接下来让我们分别展开来看看
表容量主要从表的记录数、平均长度、增长量、读写量、总大小量进行评估。一般对于OLTP的表,建议单表不要超过2000W行数据量,总大小15G以内。访问量:单表读写量在1600/s以内
查询行数据的方式:我们一般查询表数据有多少数据时用到的经典sql语句如下:
select count(*) from tableselect count(1) from table但是当数据量过大的时候,这样的查询就可能会超时,所以我们要换一种查询方式
use 库名show table status like '表名' ; 或 show table status like '表名'\G ;上述方法不仅可以查询表的数据,还可以输出表的详细信息 , 加 \G 可以格式化输出。包括表名 存储引擎 版本 行数 每行的字节数等等
查看指定数据库容量大小
select
table_schema as '数据库',
table_name as '表名',
table_rows as '记录数',
truncate(data_length/1024/1024, 2) as '数据容量(MB)',
truncate(index_length/1024/1024, 2) as '索引容量(MB)'
from information_schema.tables
order by data_length desc, index_length desc;
查询单个库中所有表磁盘占用大小
select
table_schema as '数据库',
table_name as '表名',
table_rows as '记录数',
truncate(data_length/1024/1024, 2) as '数据容量(MB)',
truncate(index_length/1024/1024, 2) as '索引容量(MB)'
from information_schema.tables
where table_schema='mysql'
order by data_length desc, index_length desc;
查询出的结果如下:

建议数据量占磁盘使用率的70%以内。同时,对于一些数据增长较快,可以考虑使用大的慢盘进行数据归档
MySQL是基于线程的服务模型,因此在一些并发较高的场景下,单实例并不能充分利用服务器的CPU资源,吞吐量反而会卡在mysql层,可以根据业务考虑自己的实例模式
上面我们已经查到我们数据表的体量了 那么为什么单表数据量越大 业务的执行效率就越慢 根本原因是什么呢?
这里提供了三种解决方案,包括数据表分区,分库分表,冷热数据归档 了解完这些方案之后大家可以选取适合自己业务的方案
表分区可以在区间内查询对应的数据,降低查询范围 并且索引分区也可以进一步提高命中率,提升查询效率
分区是指将一个表的数据按照条件分布到不同的文件上面,未分区前都是存放在一个文件上面的,但是它还是指向的同一张表,只是把数据分散到了不同文件而已。
表分区有什么好处?
WHERE语句的数据可以只保存在一个或多个分区内,这样在查找时就不用查找其他剩余的分区。因为分区可以在创建了分区表后进行修改,所以在第一次配置分区方案时还不曾这么做时,可以重新组织数据,来提高那些常用查询的效率。SUM()和COUNT()这样聚合函数的查询,可以很容易地进行并行处理。这种查询的一个简单例子如 SELECT salesperson_id, COUNT (orders) as order_total FROM sales GROUP BY salesperson_id;。通过并行,这意味着该查询可以在每个分区上同时进行,最终结果只需通过统计所有分区得到的结果。表分区的限制因素:
1024个分区。MySQL5.1中,分区表达式必须是整数,或者返回整数的表达式。在MySQL5.5中提供了非整数表达式分区的支持。MySQL的分区适用于一个表的所有数据和索引,不能只对表数据分区而不对索引分区,也不能只对索引分区而不对表分区,也不能只对表的一部分数据分区。在进行分区之前可以用如下方法 看下数据库表是否支持分区
mysql> show variables like '%partition%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| have_partitioning | YES |
+-------------------+-------+
1 row in set (0.00 sec)
建表分区
CREATE TABLE test_table (
id INT,
name VARCHAR(50),
created_at DATETIME
)
PARTITION BY RANGE (YEAR(created_at))
(
PARTITION p0 VALUES LESS THAN (2020),
PARTITION p1 VALUES LESS THAN (2021),
PARTITION p2 VALUES LESS THAN (2022),
...
PARTITION pn VALUES LESS THAN MAXVALUE
);
建索引分区
创建分区索引的语法与创建普通索引类似。我们可以在创建表时同时创建分区索引,也可以在创建表后单独创建分区索引
在建表时建立索引分区
-- 在创建表时创建分区索引
CREATE TABLE orders (
id INT,
order_date DATE,
amount DECIMAL(10, 2),
INDEX idx_order_date (order_date)
)
PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2018 VALUES LESS THAN (2019),
PARTITION p2019 VALUES LESS THAN (2020),
PARTITION p2020 VALUES LESS THAN (2021),
PARTITION p2021 VALUES LESS THAN (2022)
);
-- 创建表后单独创建分区索引
ALTER TABLE orders ADD INDEX idx_order_date (order_date) LOCAL;
在建表后建立索引分区
-- 建立索引分区
ALTER TABLE your_table
PARTITION BY RANGE (YEAR(created_at))
(
PARTITION p0 VALUES LESS THAN (2020),
PARTITION p1 VALUES LESS THAN (2021),
PARTITION p2 VALUES LESS THAN (2022),
...
PARTITION pn VALUES LESS THAN MAXVALUE
)
PARTITION BY KEY(id) PARTITIONS 4;
查询时可以添加分区,也可以不添加分区
SELECT * FROM t_order PARTITION (p0);
或者
SELECT * FROM t_order
分表后,显而易见,单表数据量降低,树的高度变低,查询经历的磁盘io变少,则可以提高效率
分库分表就是为了解决由于数据量过大而导致数据库性能降低的问题,将原来独立的数据库拆分成若干数据库组成 ,将数据大表拆分成若干数据表组成,使得单一数据库、单一数据表的数据量变小,从而达到提升数据库性能的目的。
mysql 分表分为两种:水平分表和垂直分表
库内分表和分库。比如一个表有4000万数据,查询很慢,可以分到四个表,每个表有1000万数据
join 或 union 之类的操作
拆分之前,先预估一下数据量。比如用户表有4000w数据,现在要把这些数据分到4个表user1 user2 uesr3 user4。比如id = 17,17对4取模为1,加上 ,所以这条数据存到user2表。
注意:进行水平拆分后的表要去掉auto_increment自增长。这时候的id可以用一个id 自增长临时表获得,或者使用 redis incr的方法。

优点:数据均匀的分到各个表中,出现热点问题的概率很低。
缺点:以后的数据扩容迁移比较困难难,当数据量变大之后,以前分到4个表现在要分到8个表,取模的值就变了,需要重新进行数据迁移。
点击了解 数据库之Sharding分库分表操作详解
以范围进行拆分数据,就是在某个范围内的订单,存放到某个表中。比如id=12存放到user1表,id=1300万的存放到user2 表。

优点:有利于将来对数据的扩容
缺点:如果热点数据都存在一个表中,则压力都在一个表中,其他表没有压力。
我们看到以上两种方案 都存在缺点 但是却又是互补的,那么我们将这两个方案结合会怎样呢
如下图 我们可以看到 group 组存放id 为 0~4000万 的数据,然后有三个数据库 DB0 DB1 DB2,DB0里面有四个数据库,DB1 和DB2 有三个数据库
假如id为15000 然后对10取模(为啥对10 取模 因为有10个表),取0 然后 落在DB_0,然后在根据range 范围,落在Table_0 里面。

总结:采用 hash 取模和 range 方案结合 既可以避免热点数据的问题,也有利于将来对数据的扩容
分区分表区别:
mysql 的分表是真正的分表,一张表分成很多表后,每一个小表都是完整的一张表,都对应三个文件,一个.MYD数据文件,.MYI索引文件,.frm表结构mysql性能的目的。merge来分表,是最简单的一种方式。这种方式根分区难易度差不多,并且对程序代码来说可以做到透明的。如果是用其他分表方式就比分区麻烦了。join位于不同分库的表,也无法join分表粒度不同的表,结果原本一次查询能够完成的业务,可能需要多次查询才能完成。userTable,业务要求查出成绩最好的100位,在进行分表之前,只需一个order by语句就可以搞定,但是在进行分表之后,将需要n个order by语句,分别查出每一个分表的前100名用户数据,然后再对这些数据进行合并计算,才能得出结果。为什么要冷热归档:其实原因和方案二类似,都是降低单表数据量,树的高度变低,查询经历的磁盘io变少,则可以提高效率 如果大家的业务数据,有明显的冷热区分,比如:只需要展示近一周或一个月的数据。那么这种情况这一周和一个月的数据我们称之为热数据,其余数据为冷数据。那么我们可以将冷数据归档在其他的库表中,提高我们热数据的操作效率。
创建归档表 创建的归档表 原则上要与原表保持一致
归档表数据的初始化

业务增量数据处理过程

数据的获取过程

| 方案 | 试用场景 | 优点 | 缺点 |
|---|---|---|---|
| 数据表分区 | 1.数据量较大 2.查询场景只在某个区 3.没有联合查询的场景 | 分区分表是在物理上对数据表所对应的文件进行拆分,对应的表名是不变的,所以不会影响到之前业务逻辑的sql | 分表后的查询等业务会创建对应的对象,也会造成一定的开销分区数据若要聚合的话 耗费时间也较长;使用范围不适合数据量千万级以上的 |
| 数据表分表 | 数据量较大,无法区分明显冷热区 且数据可以完整按照区间划分 | 适用于对冷热分区的界限不是很明显的数据,对后续类似的数据可以采用该方式,将大表拆分成小表 提高查询插入等效率 | 若大数据表逐渐增多 那么对应的数据库表越来越多 每个表都需要分表; 区间的划分较为固定若后续单表的数据量大起来 也会对性能造成影响;实现复杂度相对方案三比较复杂 需要测试整人实现过程 在编码层处理 对原有业务有影影响 |
| 冷热归档分库 | 数据量较大;数据冷热分区明显;冷数据使用频率极低; | 数据迁移的过程对业务的影影响较小 开发量也较少减少成本 | 需要确认分表规则 |