1.情景描述
- 线上单表数据量过大,日均产生10W条记录,一年就是接近4000千万。而且随着接入数据源的增加以及时间的推进,可以预见性的未来单表的数据量将不可避免的往亿级膨胀。
- 对于该表的操作多见于基于时间范围与指定id的查询,还有跨年,跨月的查询结果比较。
- 由于数据库版本为5.7加之数据量太大,对该表的操作都十分卡顿甚至无法进行。这对于运维人员也是灾难的。
- 综上直接的体现是涉及该表的查询的性能都很差。
2.解决方案思考分析
- 最开始是直接对sql语句进行优化,将所有涉及到该表的较为复杂的sql都精简为条件指定时间范围与id的单表查询。同时将所有计算,格式化,分组的操作去除,都放到java中进行。
优点
- 确实提升了查询性能,查询时间得到显著改善。
- sql优化不仅不影响后续在数据库层面的优化,反而在结合数据库优化后两者相辅相成带来更好的优化提升。
不足
- 基于sql本身的优化已经几乎到达极限,但是线上查询的速度依然不在可以完美接受的范围内。
- 对于该表的查询业务非常频繁,要进行优化的话,需要很大的工作量。
- 用java代码代替sql实现业务逻辑,这部分改动带来的测试与上线风险也是很高的成本。
- 伴随着sql优化的进行,我们开始思考对数据库动刀从底层解放性能。现在一般有三种方案分区,分表,分库。
分区
我目前想要采用的解决问题的方案,主要是该方案侵入性最小,如果按照天分几乎不需要改动代码。
分表
目前项目中采用的方案,方案肯定是优于分区的,但是带来的业务代码改动大,需要一一排查修改。虽然用拦截器能降低一部分代码量,但跨年的情况还是得优化。
分库
目前最不可能采用的方案,开新的服务器的成本不说,具体分库实现肯定全是坑,到时候反倒把项目搞复杂了还更麻烦。
所以综上,在已有分表的基础上,再为该表增加分区,以下是我的模拟实践。
3.实施与踩坑
出于安全保密,无法贴放原表结构,我们用结构简化的示例表来重新进行实践。新建表pt_record_now(缩减字段为3个,记录数约为3650W(模拟生成一年的数据,为2020年))
mysql> show create table pt_record_now;
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| pt_record_now | CREATE TABLE `pt_record_now` (
`id` int(11) DEFAULT NULL COMMENT '主键id',
`date` datetime DEFAULT NULL COMMENT '记录时间',
`num` decimal(10,0) DEFAULT NULL COMMENT '数据',
KEY `pt_record_now_date` (`date`) USING BTREE,
KEY `pt_record_now_id` (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
这个表基本上复制了线上的存储结构,这时我们可以很明显看到虽然它为id与date字段设计了BTREE索引,但是并没有为id与date创建联合索引。根据业务我们可以得知,id与date组合的联合索引是唯一的,也就是说明明是可以创建联合索引的。不过当时考虑到数据来源不是我们这边的系统,无法完全确定符合要求所以没有设定。如今表数据量大,加之是mysql5.7.28想要直接增加联合索引很困难。这种情况我们就准备新建一个表,再进行操作,后面再改表名就可以了。
模拟数据创建
我们先往这个表插入我们随机生成的数据。下面是我采用的java方法,代码贴出。 执行代码
@Test
public void insert3650W() {
//执行开始时间
long start = System.currentTimeMillis();
Connection conn = BaseDao.getConn();
int id = 100000;
//获取设定间隔形式下时间间隔内个个分钟
List<String> detHashList = MyDateUtil.getTimeList("2020-01-01 00:00", "2021-01-01 00:00", DateField.MINUTE, 15);
//获得循环次数
int size = detHashList.size();
//循环开始大小
int fort = 100;
//每次循环跳跃基数
int si = 100;
//添加总数
int totalNums = 0;
String sql = "insert into pt_record_now values (?,?,?)";
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
while (fort <= size) {
for (int i = 0; i < 1000; i++) {
if (fort <= size) {
for (int j = fort - si; j < fort; j++) {
ps.setString(1, id + i + "");
ps.setString(2, detHashList.get(j));
ps.setString(3, Math.ceil(Math.random() * 100) + "");
//将一组sql添加到这个批处理里面
ps.addBatch();
}
}
}
//剩余需要循环的次数
fort = fort + si;
//一组一块更新
int[] ints = ps.executeBatch();
if (ints.length > 0) {
totalNums = totalNums + ints.length;
System.out.println("已经添加: " + ints.length);
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//释放资源
BaseDao.closeAll(conn, ps);
}
//执行结束时间
long end = System.currentTimeMillis();
System.out.println("添加" + totalNums / 10000 + "W条数据所用时长" + (end - start) / 1000 + "秒");
}
获取数据库连接
public static Connection getConn() {
Connection conn = null;
try {
// rewriteBatchedStatements=true,一次插入多条数据,只插入一次
conn = (Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true", "root", "overseas");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
按照传入起始时间与时间间隔加时间格式获得其中范围内所有时间
public static List<String> getTimeList(String begin, String end,DateField unit,int step) {
List<String> hashList = new ArrayList<>();
Date bDate = cn.hutool.core.date.DateUtil.parse(begin, DatePattern.NORM_DATE_PATTERN);//yyyy-MM-dd
Date eDate = cn.hutool.core.date.DateUtil.parse(end, DatePattern.NORM_DATE_PATTERN);
List<DateTime> dateList = cn.hutool.core.date.DateUtil.rangeToList(bDate, eDate, unit,step);//创建日期范围生成器
String hash = null;
for (DateTime dt : dateList) {
hashList.add(dt.toString());
}
return hashList;
}
执行结果
添加3510W条数据所用时长449秒
Process finished with exit code 0
mysql> SELECT count(*) from pt_record_now;
+----------+
| count(*) |
+----------+
| 35100000 |
+----------+
1 row in set (32.20 sec)
可以看到插入速度非常快,3500W数据插入用时449s
数据导出
用下面的操作进行数据导出
运行结果
mysql> SELECT
-> *
-> FROM
-> pt_record_now
-> WHERE
-> date BETWEEN '2019-01-01'
-> AND '2021-12-30' INTO OUTFILE "C:/A/Soft/Mysql/DataCopy/test/pt_record_now.csv" fields terminated by ',';
Query OK, 35100000 rows affected (48.26 sec)
踩坑1
随机选择本机一个路径作为导出路径,执行导出命令时会报该路径操作不被允许; 原因是mysql对于文件导出有默认允许地址,如果不设置该功能就被禁止了,所以需要去my.ini配置文件增加该配置后然后再重启;
#默认导出地址
secure-file-priv="C:/"
服务重启成功后用命令查看是否成功;
mysql> SHOW VARIABLES LIKE 'secure%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| secure_auth | ON |
| secure_file_priv | C:\ |
+------------------+-------+
2 rows in set, 1 warning (0.00 sec)
可以看到配置成功这时再执行导出就可以了
踩坑2
我们操作的是本地的数据库,用root最高权限进行所有操作的,root账号拥有对mysql安装的系统下文件的读写能力,这里直接导出是能正常写出的。但是无论是公司的测试环境还是产品的线上环境我们肯定是没有root权限的。进行操作的账号大概率没有对系统内文件的读写权限。 所以上面的导出语句也是无法正常进行的。这时候可以曲线救国,用Navicat自带的数据导出功能导出数据。搜索Navicat导出数据的原理并没有搜索到啥结果,基本上只有一个验证原生和Navicat两种导出快慢的文章。 但是可以确定的是,Navicat的导出跟原生Mysql的导出是不一样的,且不论如何它确实帮我们从生成环境将数据以我们想要的形式拷贝下来了,这就够了。
踩坑3
踩坑2中我们用Navicat自带的导出实现了非root账号,备份生产数据库的文件并以csv的形式存储。这一切看似没有问题,但是当我们进行到后面导入数据时就会发现这个坑。如果导入数据依然采用Navicat的导入流程这是没有问题的,数据被还原的一模一样。但是我们是不能用Navicat的导入数据的功能的,原因大家应该也想到了就是速度过于缓慢了,我导入900W的数据运行16分钟才导入了将近16W。如果是4000W的数据那岂不是得接近66个小时这绝对是无法接受的,所以这个方式必须放弃。所以这里必须采用Mysql原生的导入数据的方式,到下面会具体阐述。 这里我只记录下我测试过的完美符合后面导入形式的导出选项。
- 右键导出向导
- 选择CSV下一步,下一步,下一步
- 到定义附加选项这里
- 反选包含列标题
- 文本标识符选择无
- 日期排序根据自己存储的日期格式选择(笔者是YYYY-MM-DD所以选择YMD)
- 日期分隔符输入 /
- 零填充日期否
- 时间分隔符输入 :
- 小数点符号 .
- 二进制数据编码选择Base64
- 然后接着下一步到开始就可以了
上面这些踩坑都是出现在具体测试环境实施过程,之前本地测试执行都是没有任何问题的,所以说什么东西都要亲自实现下,很多东西看着简单,但是真正实施起来会出现各种猝不及防的坑。(苦笑)
我们先不去创建分区,现在已经有了线上数据库的类似复制,咱们来走下一些sql看下它的速度到底怎么样。
mysql> EXPLAIN SELECT id,date,num FROM pt_record_now WHERE date BETWEEN '2020-01-01' AND '2020-02-01' AND id IN ( '100000', '100048', '100080' );
+----+-------------+---------------+------------+-------+-------------------------------------+------------------+---------+------+--------+----------+------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------+------------+-------+-------------------------------------+------------------+---------+------+--------+----------+------------------------------------+
| 1 | SIMPLE | pt_record_now | NULL | range | pt_record_now_date,pt_record_now_id | pt_record_now_id | 5 | NULL | 194794 | 16.59 | Using index condition; Using where |
+----+-------------+---------------+------------+-------+-------------------------------------+------------------+---------+------+--------+----------+------------------------------------+
1 row in set, 1 warning (0.00 sec)
影响线上查询速度的变量是远多于本地的比如
- 服务器CPU处理性能
- mysql有页的概念,任何的操作的最小单位都是页,所以同一页内的操作自然会很快,毕竟能减少磁盘IO,速度自然也提升了。线上的表有更多的字段(60个往上)加之Inoodb的底层数据结构带来的单个页能容纳的索引数量下降等。
- 甚至服务器的内存大小也是因素之一,当内存占用过高,这时候就会发生内存交换,带来大量的磁盘IO处理,这种恶性循环拥挤,更加会带来数据库查询速度变慢。
- 本机没有其他查询插入更新在进行,线上的mysql同一时间肯定有大量的其他sql在占用请求。
- 网络传输性能。
创建分区表
- 执行建表语句(为原表增加联合主键,因为分区要求作为分区依据的字段必须是主键或者联合主键中的一个(这里也有个令我很疑惑的地方,甚至一度让我对分区的必要性产生了怀疑,后面会具体阐述))
CREATE TABLE `pt_record_new` (
`id` INT ( 11 ) NOT NULL COMMENT '主键id',
`date` datetime NOT NULL COMMENT '记录时间',
`num` DECIMAL ( 10, 0 ) DEFAULT NULL COMMENT '数据',
PRIMARY KEY ( `date`, `id` ),
KEY `pt_record_now_date` ( `date` ) USING BTREE,
KEY `pt_record_now_id` ( `id` ) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8;
这里以date作为第一个索引id重复性太高(仅有100种),date完美符合索引模式。查询时依据最左前缀原则也可提升查询速度。 创建存储过程自动进行分区创建(默认按天划分分区,可以手动划分分区)
# 创建存储过程
## 开始标志
DELIMITER $$
## 选择使用的数据库
USE `test`$$
## 如果存在同名存储过程则删除
DROP PROCEDURE IF EXISTS `pt_record_partition`$$
## 以root权限创造存储过程(入参)
CREATE DEFINER=`root`@`%` PROCEDURE `pt_record_partition`(
IN f_year_start YEAR,
IN f_year_end YEAR,
IN f_tbname VARCHAR(64)
)
## 执行开始
BEGIN
## 全局变量声明
DECLARE v_days INT UNSIGNED DEFAULT 365;
DECLARE v_year DATE DEFAULT '2010-01-01';
DECLARE v_partition_name VARCHAR(64) DEFAULT '';
DECLARE v_log_date DATE;
DECLARE i,j INT UNSIGNED DEFAULT 1;
## 设定预处理提高执行效率
SET @stmt = '';
## concat合并多个字符(任意一个字符串为null,则返回null)
SET @stmt_begin = CONCAT('ALTER TABLE ',f_tbname,' PARTITION BY RANGE COLUMNS(date)(');
SET i = f_year_start;
WHILE i <= f_year_end DO
SET v_year = CONCAT(i,'-01-01');
## dateiff时间差额函数,datediff返回的是date2和date1之间在给定参数timeinterval下的差值
## date_add时间增加函数(时间,单位)为时间添加单位的数量的时间
SET v_days = DATEDIFF(DATE_ADD(v_year,INTERVAL 1 YEAR),v_year);
SET j = 1;
WHILE j <= v_days DO
SET v_log_date = DATE_ADD(v_year,INTERVAL j DAY);
## lpad自动左填充
SET v_partition_name = CONCAT('p',i,'_',LPAD(j,3,'0'));
SET @stmt = CONCAT(@stmt,'PARTITION ',v_partition_name,' VALUES LESS THAN(''',v_log_date,'''),');
SET j = j + 1;
END WHILE;
SET i = i + 1;
END WHILE;
SET @stmt_end = 'PARTITION p_max VALUES LESS THAN (maxvalue))';
SET @stmt = CONCAT(@stmt_begin,@stmt,@stmt_end);
## 预处理sql
PREPARE s1 FROM @stmt;
EXECUTE s1;
DROP PREPARE s1;
SELECT NULL,NULL,NULL INTO @stmt,@stmt_begin,@stmt_end;
END$$
DELIMITER ;
执行完可以看到多了个存储过程pt_record_partition,执行
mysql> call pt_record_partition(2019,2030,'pt_record_new');
Query OK, 1 row affected (1 min 27.32 sec)
Tips:mysql允许的最大分区为8192所以即使按天划分,一年365天也能划分20年往上,如果图省事就多往后划分一些时间。
划分时间分隔选择
划分时间分隔一般分按天,月,年
- 选择哪种主要看业务查询涉及哪种情景多一点,并没有直接的优劣之分。
- 比如如果查询多集中在某一天,直接用天来划分肯定是最好的,直接定位该分区。如果查询多集中在几月或者往年的几月,这种自然是选择按月
- 不过其实这里有个小细节,mysql支持的分区函数是day,month,seconds,year,但是mysql自带的分区裁剪技术却不支持month
Tips:分区裁剪技术说白了就是,MySQL 会根据 SQL 语句的过滤条件对应的分区函数进行计算,并把计算结果穿透到底层分区表从而减小扫描记录数的一种优化策略 所以用月来分区可能需要对sql进行优化或者在sql中加入hint 来提示mysql使用具体的分区,这无疑增加了负担所以我这边直接采用按天。
导入数据
直接运行sql
load data infile 'C:/A/Soft/Mysql/DataCopy/test/pt_record_now.csv' into table pt_record_new fields terminated by ',';
踩坑4
线上数据库用上面的语句是不可以的,由于加载的是本地的文件需要加上local,再运行就可以了。
踩坑5
按照上面踩坑3的导出策略,乍一看导入结果是没有什么问题的,我们这里采用的3个字段都是存在数据的。但是如果线上的字段是decimal,或者其他数字类型,直接导入空数据会被插入默认值。这样也是不行的,所以必须在插入语句中增加过滤条件,在遇到空时设定为空而不是采用默认值。 综合踩坑4,5后语句就被更改为
LOAD DATA LOCAL INFILE 'C:/A/Soft/Mysql/DataCopy/test/pt_record_now.csv' INTO TABLE pt_record_new FIELDS TERMINATED BY ',' ( `id`, `date`, @`num` )
SET `num` = NULLif( @num, '' );
不过我们这边测试是本地所以就直接执行上面的就可以了。 执行结果:
mysql> LOAD DATA LOCAL INFILE 'C:/A/Soft/Mysql/DataCopy/test/pt_record_now.csv' INTO TABLE pt_record_new FIELDS TERMINATED BY ',';
Query OK, 35100000 rows affected (7 min 58.99 sec)
Records: 35100000 Deleted: 0 Skipped: 0 Warnings: 0
3500W花费了8分钟,相对于Navicat的方案还是比较快;
验证
mysql> EXPLAIN SELECT id,date,num FROM pt_record_now WHERE date BETWEEN '2020-01-01' AND '2020-02-01' AND id IN ( '100000', '100048', '100080' );
+----+-------------+---------------+------------+-------+-------------------------------------+------------------+---------+------+--------+----------+------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------+------------+-------+-------------------------------------+------------------+---------+------+--------+----------+------------------------------------+
| 1 | SIMPLE | pt_record_now | NULL | range | pt_record_now_date,pt_record_now_id | pt_record_now_id | 5 | NULL | 194794 | 16.59 | Using index condition; Using where |
+----+-------------+---------------+------------+-------+-------------------------------------+------------------+---------+------+--------+----------+------------------------------------+
1 row in set, 1 warning (0.03 sec)
mysql> EXPLAIN SELECT id,date,num FROM pt_record_new WHERE date BETWEEN '2020-01-01' AND '2020-02-01' AND id IN ( '100000', '100048', '100080' );
+----+-------------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+---------------------------------------------+------------------+---------+------+------+----------+----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+---------------------------------------------+------------------+---------+------+------+----------+----------------------------------+
| 1 | SIMPLE | pt_record_new | p2020_001,p2020_002,p2020_003,p2020_004,p2020_005,p2020_006,p2020_007,p2020_008,p2020_009,p2020_010,p2020_011,p2020_012,p2020_013,p2020_014,p2020_015,p2020_016,p2020_017,p2020_018,p2020_019,p2020_020,p2020_021,p2020_022,p2020_023,p2020_024,p2020_025,p2020_026,p2020_027,p2020_028,p2020_029,p2020_030,p2020_031,p2020_032 | range | PRIMARY,pt_record_now_date,pt_record_now_id | pt_record_now_id | 9 | NULL | 8931 | 100.00 | Using index condition; Using MRR |
+----+-------------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+---------------------------------------------+------------------+---------+------+------+----------+----------------------------------+
1 row in set, 1 warning (0.00 sec)
能看到相较于原先的命中提升了不少(194794 -> 8931)
再来看下更小的时间范围的效果。
mysql> EXPLAIN SELECT id,date,num FROM pt_record_now WHERE date BETWEEN '2020-01-01' AND '2020-01-02' AND id IN ( '100000', '100048', '100080' );
+----+-------------+---------------+------------+-------+-------------------------------------+--------------------+---------+------+--------+----------+------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------+------------+-------+-------------------------------------+--------------------+---------+------+--------+----------+------------------------------------+
| 1 | SIMPLE | pt_record_now | NULL | range | pt_record_now_date,pt_record_now_id | pt_record_now_date | 6 | NULL | 178690 | 0.56 | Using index condition; Using where |
+----+-------------+---------------+------------+-------+-------------------------------------+--------------------+---------+------+--------+----------+------------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT id,date,num FROM pt_record_new WHERE date BETWEEN '2020-01-01' AND '2020-01-02' AND id IN ( '100000', '100048', '100080' );
+----+-------------+---------------+---------------------+-------+---------------------------------------------+------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------+---------------------+-------+---------------------------------------------+------------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | pt_record_new | p2020_001,p2020_002 | range | PRIMARY,pt_record_now_date,pt_record_now_id | pt_record_now_id | 9 | NULL | 291 | 100.00 | Using index condition |
+----+-------------+---------------+---------------------+-------+---------------------------------------------+------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
相较于上面的对比这里就比较有意思了,我们明明将时间缩进了很多,按照惯性对原先表的命中也应该有提升,结果相对与 194794 -> 178690 有提升但是并不明显,而按天分区方案效果提升就非常明显了 8931 -> 291,相较于老方案就更加夸张了。
疑惑
在进行分区时,又创建了个表,只是这个表没有进行分区。但是相对于生产上的表还是增加了联合主键。这里主要是想对比,有无分区在已经有联合主键的情况下是否有区别。这也是我最困惑的地方。 结果如下
mysql> EXPLAIN SELECT id,date,num FROM pt_record_now_key WHERE date BETWEEN '2020-01-01' AND '2020-02-01' AND id IN ( '100000', '100048', '100080' );
+----+-------------+-------------------+------------+-------+---------------------------------------------+------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------------+------------+-------+---------------------------------------------+------------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | pt_record_now_key | NULL | range | PRIMARY,pt_record_now_date,pt_record_now_id | pt_record_now_id | 9 | NULL | 8931 | 100.00 | Using index condition |
+----+-------------+-------------------+------------+-------+---------------------------------------------+------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.06 sec)
mysql> EXPLAIN SELECT id,date,num FROM pt_record_now_key WHERE date BETWEEN '2020-01-01' AND '2020-01-02' AND id IN ( '100000', '100048', '100080' );
+----+-------------+-------------------+------------+-------+---------------------------------------------+------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------------+------------+-------+---------------------------------------------+------------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | pt_record_now_key | NULL | range | PRIMARY,pt_record_now_date,pt_record_now_id | pt_record_now_id | 9 | NULL | 291 | 100.00 | Using index condition |
+----+-------------+-------------------+------------+-------+---------------------------------------------+------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.01 sec)
可以看到即使没有分区,就命中而言分区与未分区并没有多大区别。所以可以得知有比较好的索引时,分区相较于索引并没有带来查询速度的提升,甚至可能带来负提升。 不过分区的优势还有
- 查看data文件可以看到分区方案将该表拆分成了每个分区对应的文件。意味着在物理结构上两者还是有本质的区别。所以当数据量非常大的时候,分区方案可以带来更好的磁盘IO性能。
- 由于进行了分区,即使某一个分区文件损坏,也不会影响对该表的使用。
- 提高了可维护性。对单个分区的维护,并不会影响其他分区。这在数据量比较大的情景下,对于运维人员还是很有必要性的。
4.总结
为表进行分区,就操作而言其实就一句sql,但是我实践过程中踩了不少坑,特别记录下。很多东西说起来简单实践起来坑太多了。不过这些东西只有自己亲自实现下才能有更深的理解。优化大数据量的场景最好还是结合分区和分表,先按照年为大表分表,然后再进行分区。所以接下来得好好学学如何实践分表了。