当前位置: 首页 > news >正文

记录下MySQL的分区表

记录下MySQL的分区表

分区表的结构

audit_log 表的结构定义如下:

mysql> SHOW CREATE TABLE audit_log\G
*************************** 1. row ***************************Table: audit_log
Create Table: CREATE TABLE `audit_log` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '日志ID',`log_time` datetime NOT NULL COMMENT '日志产生时间',`operator_id` varchar(64) NOT NULL COMMENT '操作人ID (索引)',`operator_name` varchar(128) NOT NULL COMMENT '操作人名称快照',`action_type` varchar(50) NOT NULL COMMENT '操作类型',`target_type` varchar(50) NOT NULL COMMENT '目标类型',`target_id` varchar(64) NOT NULL COMMENT '目标ID',`target_name` varchar(255) NOT NULL COMMENT '目标名称快照',`action_summary` varchar(500) NOT NULL COMMENT '操作摘要描述',`change_count` int(10) unsigned DEFAULT '0' COMMENT '变更项数量',`entity_count` int(10) unsigned DEFAULT '0' COMMENT '涉及实体数量',PRIMARY KEY (`id`),KEY `aal_idx_log_time` (`log_time`),KEY `aal_idx_operator` (`operator_id`),KEY `aal_idx_target` (`target_type`,`target_id`),KEY `aal_idx_summary` (`action_summary`(100))
) ENGINE=MyISAM AUTO_INCREMENT=14650005 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

表中模拟了 1400 万数据(从2021-07-19至2025-07-22每天一万条):

mysql> SELECT COUNT(*) FROM audit_log;
+----------+
| COUNT(*) |
+----------+
| 14650004 |
+----------+
1 row in set (0.00 sec)mysql>

分区前的数据表文件:

➜  data ll demo_db/audit_log*
-rw-r----- 1 dnsmasq systemd-journal  13K  7月 23 11:32 demo_db/audit_log.frm
-rw-r----- 1 dnsmasq systemd-journal 2.2G  7月 23 11:33 demo_db/audit_log.MYD
-rw-r----- 1 dnsmasq systemd-journal 562M  7月 23 11:35 demo_db/audit_log.MYI
➜  data

这个mysql容器之前调试时设置成了 MyISAM,分区表建议使用 InnoDB 存储引擎:

  1. MyISAM 不支持事务,没有 Redo log,无法保证服务崩溃后的数据一致性。
  2. MyISAM 只有表级锁,虽然写入操作只涉及一个分区,但是还是会锁定整张表。
  3. MyISAM 存储引擎下,每个分区都会生成一个 .MYD 和 .MYI 文件,如果是按月或按日的分区,会导致文件系统 inode 和目录项激增,也会快速消耗服务器的文件描述符资源 (open_files_limit),可能导致 Can't open file 错误。

转换为按季度的分区表

步骤1: 移除原自增主键

mysql> ALTER TABLE audit_log DROP PRIMARY KEY, MODIFY id BIGINT UNSIGNED NOT NULL;
Query OK, 14650004 rows affected (2 min 49.20 sec)mysql>

步骤2: 添加复合主键 (id + log_time),分区键必须属于主键(是主键或是主键的一部分)

mysql> ALTER TABLE audit_log ADD PRIMARY KEY (id, log_time);
Query OK, 14650004 rows affected (3 min 5.18 sec)mysql> 

步骤3: 定义分区方案(按季度分区)

ALTER TABLE audit_log 
PARTITION BY RANGE COLUMNS(log_time) (PARTITION p2021Q3 VALUES LESS THAN ('2021-10-01'),PARTITION p2021Q4 VALUES LESS THAN ('2022-01-01'),PARTITION p2022Q1 VALUES LESS THAN ('2022-04-01'),PARTITION p2022Q2 VALUES LESS THAN ('2022-07-01'),PARTITION p2022Q3 VALUES LESS THAN ('2022-10-01'),PARTITION p2022Q4 VALUES LESS THAN ('2023-01-01'),PARTITION p2023Q1 VALUES LESS THAN ('2023-04-01'),PARTITION p2023Q2 VALUES LESS THAN ('2023-07-01'),PARTITION p2023Q3 VALUES LESS THAN ('2023-10-01'),PARTITION p2023Q4 VALUES LESS THAN ('2024-01-01'),PARTITION p2024Q1 VALUES LESS THAN ('2024-04-01'),PARTITION p2024Q2 VALUES LESS THAN ('2024-07-01'),PARTITION p2024Q3 VALUES LESS THAN ('2024-10-01'),PARTITION p2024Q4 VALUES LESS THAN ('2025-01-01'),PARTITION p2025Q1 VALUES LESS THAN ('2025-04-01'),PARTITION p2025Q2 VALUES LESS THAN ('2025-07-01'),PARTITION p2025Q3 VALUES LESS THAN ('2025-10-01'),PARTITION p_future VALUES LESS THAN (MAXVALUE)
);mysql> ALTER TABLE audit_log -> PARTITION BY RANGE COLUMNS(log_time) (-> PARTITION p2021Q3 VALUES LESS THAN ('2021-10-01'),-> PARTITION p2021Q4 VALUES LESS THAN ('2022-01-01'),-> PARTITION p2022Q1 VALUES LESS THAN ('2022-04-01'),-> PARTITION p2022Q2 VALUES LESS THAN ('2022-07-01'),-> PARTITION p2022Q3 VALUES LESS THAN ('2022-10-01'),-> PARTITION p2022Q4 VALUES LESS THAN ('2023-01-01'),-> PARTITION p2023Q1 VALUES LESS THAN ('2023-04-01'),-> PARTITION p2023Q2 VALUES LESS THAN ('2023-07-01'),-> PARTITION p2023Q3 VALUES LESS THAN ('2023-10-01'),-> PARTITION p2023Q4 VALUES LESS THAN ('2024-01-01'),-> PARTITION p2024Q1 VALUES LESS THAN ('2024-04-01'),-> PARTITION p2024Q2 VALUES LESS THAN ('2024-07-01'),-> PARTITION p2024Q3 VALUES LESS THAN ('2024-10-01'),-> PARTITION p2024Q4 VALUES LESS THAN ('2025-01-01'),-> PARTITION p2025Q1 VALUES LESS THAN ('2025-04-01'),-> PARTITION p2025Q2 VALUES LESS THAN ('2025-07-01'),-> PARTITION p2025Q3 VALUES LESS THAN ('2025-10-01'),-> PARTITION p_future VALUES LESS THAN (MAXVALUE)-> );
Query OK, 14650004 rows affected, 1 warning (2 min 17.65 sec)mysql> 

分区之后的数据表文件:

➜  data ll demo_db/audit_log*
-rw-r----- 1 dnsmasq systemd-journal  13K  7月 23 11:38 demo_db/audit_log.frm
-rw-r----- 1 dnsmasq systemd-journal  184  7月 23 11:38 demo_db/audit_log.par
-rw-r----- 1 dnsmasq systemd-journal 112M  7月 23 11:38 demo_db/audit_log#P#p2021Q3.MYD
-rw-r----- 1 dnsmasq systemd-journal  92M  7月 23 11:38 demo_db/audit_log#P#p2021Q3.MYI
-rw-r----- 1 dnsmasq systemd-journal 139M  7月 23 11:38 demo_db/audit_log#P#p2021Q4.MYD
-rw-r----- 1 dnsmasq systemd-journal 114M  7月 23 11:38 demo_db/audit_log#P#p2021Q4.MYI
-rw-r----- 1 dnsmasq systemd-journal 136M  7月 23 11:38 demo_db/audit_log#P#p2022Q1.MYD
-rw-r----- 1 dnsmasq systemd-journal 112M  7月 23 11:39 demo_db/audit_log#P#p2022Q1.MYI
-rw-r----- 1 dnsmasq systemd-journal 138M  7月 23 11:39 demo_db/audit_log#P#p2022Q2.MYD
-rw-r----- 1 dnsmasq systemd-journal 113M  7月 23 11:39 demo_db/audit_log#P#p2022Q2.MYI
-rw-r----- 1 dnsmasq systemd-journal 139M  7月 23 11:39 demo_db/audit_log#P#p2022Q3.MYD
-rw-r----- 1 dnsmasq systemd-journal 114M  7月 23 11:39 demo_db/audit_log#P#p2022Q3.MYI
-rw-r----- 1 dnsmasq systemd-journal 139M  7月 23 11:39 demo_db/audit_log#P#p2022Q4.MYD
-rw-r----- 1 dnsmasq systemd-journal 114M  7月 23 11:39 demo_db/audit_log#P#p2022Q4.MYI
-rw-r----- 1 dnsmasq systemd-journal 136M  7月 23 11:39 demo_db/audit_log#P#p2023Q1.MYD
-rw-r----- 1 dnsmasq systemd-journal 112M  7月 23 11:39 demo_db/audit_log#P#p2023Q1.MYI
-rw-r----- 1 dnsmasq systemd-journal 138M  7月 23 11:39 demo_db/audit_log#P#p2023Q2.MYD
-rw-r----- 1 dnsmasq systemd-journal 113M  7月 23 11:39 demo_db/audit_log#P#p2023Q2.MYI
-rw-r----- 1 dnsmasq systemd-journal 139M  7月 23 11:39 demo_db/audit_log#P#p2023Q3.MYD
-rw-r----- 1 dnsmasq systemd-journal 114M  7月 23 11:39 demo_db/audit_log#P#p2023Q3.MYI
-rw-r----- 1 dnsmasq systemd-journal 139M  7月 23 11:39 demo_db/audit_log#P#p2023Q4.MYD
-rw-r----- 1 dnsmasq systemd-journal 114M  7月 23 11:39 demo_db/audit_log#P#p2023Q4.MYI
-rw-r----- 1 dnsmasq systemd-journal 138M  7月 23 11:39 demo_db/audit_log#P#p2024Q1.MYD
-rw-r----- 1 dnsmasq systemd-journal 113M  7月 23 11:40 demo_db/audit_log#P#p2024Q1.MYI
-rw-r----- 1 dnsmasq systemd-journal 138M  7月 23 11:40 demo_db/audit_log#P#p2024Q2.MYD
-rw-r----- 1 dnsmasq systemd-journal 113M  7月 23 11:40 demo_db/audit_log#P#p2024Q2.MYI
-rw-r----- 1 dnsmasq systemd-journal 139M  7月 23 11:40 demo_db/audit_log#P#p2024Q3.MYD
-rw-r----- 1 dnsmasq systemd-journal 114M  7月 23 11:40 demo_db/audit_log#P#p2024Q3.MYI
-rw-r----- 1 dnsmasq systemd-journal 139M  7月 23 11:40 demo_db/audit_log#P#p2024Q4.MYD
-rw-r----- 1 dnsmasq systemd-journal 114M  7月 23 11:40 demo_db/audit_log#P#p2024Q4.MYI
-rw-r----- 1 dnsmasq systemd-journal 136M  7月 23 11:40 demo_db/audit_log#P#p2025Q1.MYD
-rw-r----- 1 dnsmasq systemd-journal 112M  7月 23 11:40 demo_db/audit_log#P#p2025Q1.MYI
-rw-r----- 1 dnsmasq systemd-journal 138M  7月 23 11:40 demo_db/audit_log#P#p2025Q2.MYD
-rw-r----- 1 dnsmasq systemd-journal 113M  7月 23 11:40 demo_db/audit_log#P#p2025Q2.MYI
-rw-r----- 1 dnsmasq systemd-journal  34M  7月 23 11:40 demo_db/audit_log#P#p2025Q3.MYD
-rw-r----- 1 dnsmasq systemd-journal  28M  7月 23 11:40 demo_db/audit_log#P#p2025Q3.MYI
-rw-r----- 1 dnsmasq systemd-journal    0  7月 23 11:38 demo_db/audit_log#P#p_future.MYD
-rw-r----- 1 dnsmasq systemd-journal 2.0K  7月 23 11:38 demo_db/audit_log#P#p_future.MYI
➜  data 

转换后的常规查询对比

按原主键 id 的查询

mysql> use demo_db;Database changed
mysql> 
mysql> EXPLAIN SELECT * FROM audit_log WHERE id = 10000001\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: p2021Q3,p2021Q4,p2022Q1,p2022Q2,p2022Q3,p2022Q4,p2023Q1,p2023Q2,p2023Q3,p2023Q4,p2024Q1,p2024Q2,p2024Q3,p2024Q4,p2025Q1,p2025Q2,p2025Q3,p_futuretype: ref
possible_keys: PRIMARYkey: PRIMARYkey_len: 8ref: constrows: 15filtered: 100.00Extra: NULL
1 row in set, 1 warning (0.00 sec)mysql> mysql> use demo_db_bak;Database changed
mysql> 
mysql> EXPLAIN SELECT * FROM audit_log WHERE id = 10000001\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: NULLtype: const
possible_keys: PRIMARYkey: PRIMARYkey_len: 8ref: constrows: 1filtered: 100.00Extra: NULL
1 row in set, 1 warning (0.00 sec)mysql> 

直接按 id 查询,分区后的 type 是 ref,分区前的是 const

type显示的是访问类型,访问类型表示我是以何种方式去访问我们的数据,最容易想的是全表扫描,直接暴力的遍历一张表去寻找需要的数据,效率非常低下。

访问的类型有很多,效率从最好到最坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

rows: 15 表示优化器预估需检查15行(约等于分区数量),实际是扫描所有分区的主键索引。

按普通索引字段的查询

mysql> EXPLAIN SELECT * FROM audit_log WHERE operator_id = 'user_000001'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: p2021Q3,p2021Q4,p2022Q1,p2022Q2,p2022Q3,p2022Q4,p2023Q1,p2023Q2,p2023Q3,p2023Q4,p2024Q1,p2024Q2,p2024Q3,p2024Q4,p2025Q1,p2025Q2,p2025Q3,p_futuretype: ref
possible_keys: aal_idx_operatorkey: aal_idx_operatorkey_len: 258ref: constrows: 15filtered: 100.00Extra: NULL
1 row in set, 1 warning (0.00 sec)mysql>mysql> EXPLAIN SELECT * FROM audit_log WHERE operator_id = 'user_000001'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: NULLtype: ref
possible_keys: aal_idx_operatorkey: aal_idx_operatorkey_len: 258ref: constrows: 1filtered: 100.00Extra: NULL
1 row in set, 1 warning (0.00 sec)mysql> 

按 log_time 范围的查询(跨分区和不跨分区)

mysql> EXPLAIN SELECT * FROM audit_log WHERE log_time between '2025-03-31 00:00:00' and '2025-04-01 23:59:59'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: p2025Q1,p2025Q2type: range
possible_keys: aal_idx_log_timekey: aal_idx_log_timekey_len: 5ref: NULLrows: 22191filtered: 100.00Extra: Using index condition
1 row in set, 1 warning (0.00 sec)mysql>
mysql> EXPLAIN SELECT * FROM audit_log WHERE log_time between '2025-04-01 00:00:00' and '2025-04-01 23:59:59'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: p2025Q2type: range
possible_keys: aal_idx_log_timekey: aal_idx_log_timekey_len: 5ref: NULLrows: 9472filtered: 100.00Extra: Using index condition
1 row in set, 1 warning (0.00 sec)mysql>mysql> EXPLAIN SELECT * FROM audit_log WHERE log_time between '2025-03-31 00:00:00' and '2025-04-01 23:59:59'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: NULLtype: range
possible_keys: aal_idx_log_timekey: aal_idx_log_timekey_len: 5ref: NULLrows: 16468filtered: 100.00Extra: Using index condition
1 row in set, 1 warning (0.00 sec)mysql> 
mysql> EXPLAIN SELECT * FROM audit_log WHERE log_time between '2025-04-01 00:00:00' and '2025-04-01 23:59:59'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: NULLtype: range
possible_keys: aal_idx_log_timekey: aal_idx_log_timekey_len: 5ref: NULLrows: 8234filtered: 100.00Extra: Using index condition
1 row in set, 1 warning (0.01 sec)mysql>

程序中的查询时间对比

没有指定 log_time 范围,分区后会检索所有分区,查询变慢;

// demo_db 库,1.03ms
// [2025-07-23 14:06:04][1.03]select count(*) as aggregate from `audit_log`
$count = DB::connection('mysql')->table('audit_log')->count();
echo 'count = ' . $count . PHP_EOL; // count = 14650004// demo_db_bak 库,0.58ms
[2025-07-23 14:06:04][0.58]select count(*) as aggregate from `audit_log`
$count = DB::connection('mysql_test')->table('audit_log')->count();
echo 'count = ' . $count . PHP_EOL; // count = 14650004

// demo_db 库,12.67ms
// [2025-07-23 14:08:57][12.67]select count(*) as aggregate from `audit_log` where `operator_id` = 'user_000001'
$count = DB::connection('mysql')->table('audit_log')->where('operator_id', '=', 'user_000001')->count();
echo 'count = ' . $count . PHP_EOL; // count = 1// demo_db_bak 库,0.68ms
// [2025-07-23 14:08:57][0.68]select count(*) as aggregate from `audit_log` where `operator_id` = 'user_000001'
$count = DB::connection('mysql_test')->table('audit_log')->where('operator_id', '=', 'user_000001')->count();
echo 'count = ' . $count . PHP_EOL; // count = 1

// demo_db 库,80.81ms
[2025-07-23 14:15:12][80.81]select count(*) as aggregate from `audit_log` where `log_time` between '2025-07-01 00:00:00' and '2025-07-31 23:59:59' and `operator_id` = 'user_000001'
$count = DB::connection('mysql')->table('audit_log')->whereBetween('log_time', ['2025-07-01 00:00:00', '2025-07-31 23:59:59'])->where('operator_id', '=', 'user_000001')->count();
echo 'count = ' . $count . PHP_EOL;// demo_db_bak 库,319.2ms
[2025-07-23 14:15:12][319.2]select count(*) as aggregate from `audit_log` where `log_time` between '2025-07-01 00:00:00' and '2025-07-31 23:59:59' and `operator_id` = 'user_000001'
$count = DB::connection('mysql_test')->table('audit_log')->whereBetween('log_time', ['2025-07-01 00:00:00', '2025-07-31 23:59:59'])->where('operator_id', '=', 'user_000001')->count();
echo 'count = ' . $count . PHP_EOL;

查看分区数据分布

mysql> SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'audit_log';
+----------------+------------+
| PARTITION_NAME | TABLE_ROWS |
+----------------+------------+
| p2021Q3        |     740000 |
| p2021Q4        |     920000 |
| p2022Q1        |     900000 |
| p2022Q2        |     910000 |
| p2022Q3        |     920000 |
| p2022Q4        |     920000 |
| p2023Q1        |     900000 |
| p2023Q2        |     910000 |
| p2023Q3        |     920000 |
| p2023Q4        |     920000 |
| p2024Q1        |     910000 |
| p2024Q2        |     910000 |
| p2024Q3        |     920000 |
| p2024Q4        |     920000 |
| p2025Q1        |     900000 |
| p2025Q2        |     910000 |
| p2025Q3        |     220004 |
| p_future       |          0 |
| NULL           |   14650004 |
+----------------+------------+
19 rows in set (0.00 sec)mysql> 

在摘要描述字段上的模糊搜索

摘要描述字段上的索引是 aal_idx_summary (action_summary(100)),使用 “%搜索内容%” 时索引不生效,对比下此时的查询:

mysql> SELECT COUNT(*) FROM audit_log WHERE log_time > '2025-07-01 00:00:00'\G
*************************** 1. row ***************************
COUNT(*): 220004
1 row in set (0.02 sec)mysql> 
mysql> EXPLAIN SELECT COUNT(*) FROM audit_log WHERE log_time > '2025-07-01 00:00:00' AND action_summary LIKE '%系统配置%'\G 
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: p2025Q3,p_futuretype: ALL
possible_keys: aal_idx_log_timekey: NULLkey_len: NULLref: NULLrows: 220004filtered: 11.11Extra: Using where
1 row in set, 2 warnings (0.00 sec)mysql> 
mysql> SELECT COUNT(*) FROM audit_log WHERE log_time > '2025-07-01 00:00:00' AND action_summary LIKE '%系统配置%'\G
*************************** 1. row ***************************
COUNT(*): 55001
1 row in set (0.06 sec)mysql>mysql> SELECT COUNT(*) FROM audit_log WHERE log_time > '2025-07-01 00:00:00'\G
*************************** 1. row ***************************
COUNT(*): 220004
1 row in set (0.03 sec)mysql> 
mysql> EXPLAIN SELECT COUNT(*) FROM audit_log WHERE log_time > '2025-07-01 00:00:00' AND action_summary LIKE '%系统配置%'\G 
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: NULLtype: range
possible_keys: aal_idx_log_timekey: aal_idx_log_timekey_len: 5ref: NULLrows: 152015filtered: 11.11Extra: Using index condition; Using where
1 row in set, 1 warning (0.01 sec)mysql>
mysql> SELECT COUNT(*) FROM audit_log WHERE log_time > '2025-07-01 00:00:00' AND action_summary LIKE '%系统配置%'\G
*************************** 1. row ***************************
COUNT(*): 55001
1 row in set (0.27 sec)mysql>

MySQL 5.6+ 并且是InnoDB时,可以增加全文索引

ALTER TABLE audit_log ADD FULLTEXT ft_action_summary (action_summary);

查询时使用:(执行计划将从 ALL → fulltext,性能提升10倍以上):

SELECT COUNT(*) 
FROM audit_log 
WHERE log_time > '2025-07-01'AND MATCH(action_summary) AGAINST('系统配置' IN BOOLEAN MODE);

按 log_time 进行分区后,禁止在该字段上做函数转换

-- 错误示范
SELECT * FROM audit_log WHERE DATE(log_time) BETWEEN '2024-03-16' AND '2024-04-15';
SELECT * FROM audit_log WHERE YEAR(log_time) = 2024 AND QUARTER(log_time) = 2;-- 正确示范:
SELECT * FROM audit_log WHERE log_time >= '2024-03-16 00:00:00' AND log_time <= '2024-04-15 23:59:59';
SELECT * FROM audit_log WHERE log_time >= '2024-04-01 00:00:00' AND log_time <= '2024-06-30 23:59:59';
  1. 破坏索引使用:对字段使用函数后,MySQL 无法使用该字段的索引;
  2. 破坏分区裁剪:分区优化依赖字段的原始值,函数处理会使优化器无法识别分区边界;
  3. 强制全表扫描:每次查询都需要对所有分区数据执行函数计算;

查询优化

当查询条件跨越多个分区(如 WHERE log_time BETWEEN '2024-03-16' AND '2024-04-15' 跨越Q1和Q2分区)时,可采取以下优化策略:
分区裁剪优化(核心策略)

确保分区裁剪生效,禁止在分区字段上使用函数

-- 禁止在分区字段(这里是 log_time)上使用函数
SELECT * FROM audit_log WHERE YEAR(log_time) = 2025;
-- 确保使用直接日期比较
SELECT count(*) FROM audit_log WHERE log_time >= '2025-01-01 00:00:00';-- 明确指定分区范围(当已知分区时)
SELECT * FROM audit_log PARTITION (p2024Q1, p2024Q2) WHERE log_time BETWEEN '2024-03-16' AND '2024-04-15';

覆盖索引+分区提示优化

-- 未优化查询 (跨两个季度分区)
SELECT COUNT(*) FROM audit_log WHERE log_time >= '2024-03-16 00:00:00' AND log_time <= '2024-04-15 23:59:59' AND action_type = 'UPDATE';-- 添加覆盖索引
ALTER TABLE audit_log ADD INDEX idx_cross_partition (log_time, action_type, operator_id);-- 优化后 (覆盖索引+分区提示)
SELECT COUNT(*) FROM audit_log PARTITION (p2024Q1, p2024Q2) USE INDEX (idx_cross_partition) WHERE log_time BETWEEN '2024-03-16 00:00:00' AND '2024-04-15 23:59:59' AND action_type = 'UPDATE';

-- 未优化查询 (跨两个季度分区)
mysql> SELECT COUNT(*) FROM audit_log WHERE log_time >= '2024-03-16 00:00:00' AND log_time <= '2024-04-15 23:59:59' AND action_type = 'UPDATE'\G
*************************** 1. row ***************************
COUNT(*): 0
1 row in set (0.38 sec)mysql> 
mysql> 
mysql> EXPLAIN SELECT COUNT(*) FROM audit_log WHERE log_time >= '2024-03-16 00:00:00' AND log_time <= '2024-04-15 23:59:59'AND action_type = 'UPDATE'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: p2024Q1,p2024Q2type: range
possible_keys: aal_idx_log_timekey: aal_idx_log_timekey_len: 5ref: NULLrows: 323026filtered: 10.00Extra: Using index condition; Using where
1 row in set, 1 warning (0.00 sec)mysql> mysql> ALTER TABLE audit_log ADD INDEX idx_cross_partition (log_time, action_type, operator_id);
Query OK, 14650004 rows affected, 1 warning (2 min 49.10 sec)mysql> -- 优化后 (覆盖索引+分区提示)
mysql> SELECT COUNT(*) FROM audit_log PARTITION (p2024Q1, p2024Q2) USE INDEX (idx_cross_partition) WHERE log_time BETWEEN '2024-03-16 00:00:00' AND '2024-04-15 23:59:59' AND action_type = 'UPDATE'\G
*************************** 1. row ***************************
COUNT(*): 0
1 row in set (0.09 sec)mysql> 
mysql> EXPLAIN SELECT COUNT(*) FROM audit_log PARTITION (p2024Q1, p2024Q2) USE INDEX (idx_cross_partition) WHERE log_time BETWEEN
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: audit_logpartitions: p2024Q1,p2024Q2type: range
possible_keys: idx_cross_partitionkey: idx_cross_partitionkey_len: 207ref: NULLrows: 362118filtered: 10.00Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)mysql> 

添加覆盖索引前的执行计划:Using index condition; Using where,先过滤时间,再回表过滤 action_type。

添加覆盖索引后的执行计划:Using where; Using index,直接在覆盖索引中完成过滤;

历史数据的归档

历史数据归档后,系统中如果还需要提供这部分数据的查询服务,需要提供单独的查询入口,或者在查询的业务逻辑中根据查询的日期范围切换所连接的数据表(或数据库)。

步骤1:创建与分区结构完全相同的归档表

CREATE TABLE audit_log_archive_2021Q3 LIKE audit_log;-- 移除归档表的分区定义(使其成为普通表)
ALTER TABLE audit_log_archive_2021Q3 REMOVE PARTITIONING;

操作记录:

mysql> CREATE TABLE audit_log_archive_2021Q3 LIKE audit_log;
Query OK, 0 rows affected, 1 warning (0.02 sec)mysql>
mysql> SHOW CREATE TABLE audit_log_archive_2021Q3\G
*************************** 1. row ***************************Table: audit_log_archive_2021Q3
Create Table: CREATE TABLE `audit_log_archive_2021Q3` (`id` bigint(20) unsigned NOT NULL,`log_time` datetime NOT NULL COMMENT '日志产生时间',`operator_id` varchar(64) NOT NULL COMMENT '操作人ID (索引)',`operator_name` varchar(128) NOT NULL COMMENT '操作人名称快照',`action_type` varchar(50) NOT NULL COMMENT '操作类型',`target_type` varchar(50) NOT NULL COMMENT '目标类型',`target_id` varchar(64) NOT NULL COMMENT '目标ID',`target_name` varchar(255) NOT NULL COMMENT '目标名称快照',`action_summary` varchar(500) NOT NULL COMMENT '操作摘要描述',`change_count` int(10) unsigned DEFAULT '0' COMMENT '变更项数量',`entity_count` int(10) unsigned DEFAULT '0' COMMENT '涉及实体数量',PRIMARY KEY (`id`,`log_time`),KEY `aal_idx_log_time` (`log_time`),KEY `aal_idx_operator` (`operator_id`),KEY `aal_idx_target` (`target_type`,`target_id`),KEY `aal_idx_summary` (`action_summary`(100)),KEY `idx_cross_partition` (`log_time`,`action_type`,`operator_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
/*!50500 PARTITION BY RANGE  COLUMNS(log_time)
(PARTITION p2021Q3 VALUES LESS THAN ('2021-10-01') ENGINE = MyISAM,PARTITION p2021Q4 VALUES LESS THAN ('2022-01-01') ENGINE = MyISAM,PARTITION p2022Q1 VALUES LESS THAN ('2022-04-01') ENGINE = MyISAM,PARTITION p2022Q2 VALUES LESS THAN ('2022-07-01') ENGINE = MyISAM,PARTITION p2022Q3 VALUES LESS THAN ('2022-10-01') ENGINE = MyISAM,PARTITION p2022Q4 VALUES LESS THAN ('2023-01-01') ENGINE = MyISAM,PARTITION p2023Q1 VALUES LESS THAN ('2023-04-01') ENGINE = MyISAM,PARTITION p2023Q2 VALUES LESS THAN ('2023-07-01') ENGINE = MyISAM,PARTITION p2023Q3 VALUES LESS THAN ('2023-10-01') ENGINE = MyISAM,PARTITION p2023Q4 VALUES LESS THAN ('2024-01-01') ENGINE = MyISAM,PARTITION p2024Q1 VALUES LESS THAN ('2024-04-01') ENGINE = MyISAM,PARTITION p2024Q2 VALUES LESS THAN ('2024-07-01') ENGINE = MyISAM,PARTITION p2024Q3 VALUES LESS THAN ('2024-10-01') ENGINE = MyISAM,PARTITION p2024Q4 VALUES LESS THAN ('2025-01-01') ENGINE = MyISAM,PARTITION p2025Q1 VALUES LESS THAN ('2025-04-01') ENGINE = MyISAM,PARTITION p2025Q2 VALUES LESS THAN ('2025-07-01') ENGINE = MyISAM,PARTITION p2025Q3 VALUES LESS THAN ('2025-10-01') ENGINE = MyISAM,PARTITION p_future VALUES LESS THAN (MAXVALUE) ENGINE = MyISAM) */
1 row in set, 1 warning (0.00 sec)mysql> 
mysql> ALTER TABLE audit_log_archive_2021Q3 REMOVE PARTITIONING;
Query OK, 0 rows affected (0.02 sec)mysql> 
mysql> SHOW CREATE TABLE audit_log_archive_2021Q3\G
*************************** 1. row ***************************Table: audit_log_archive_2021Q3
Create Table: CREATE TABLE `audit_log_archive_2021Q3` (`id` bigint(20) unsigned NOT NULL,`log_time` datetime NOT NULL COMMENT '日志产生时间',`operator_id` varchar(64) NOT NULL COMMENT '操作人ID (索引)',`operator_name` varchar(128) NOT NULL COMMENT '操作人名称快照',`action_type` varchar(50) NOT NULL COMMENT '操作类型',`target_type` varchar(50) NOT NULL COMMENT '目标类型',`target_id` varchar(64) NOT NULL COMMENT '目标ID',`target_name` varchar(255) NOT NULL COMMENT '目标名称快照',`action_summary` varchar(500) NOT NULL COMMENT '操作摘要描述',`change_count` int(10) unsigned DEFAULT '0' COMMENT '变更项数量',`entity_count` int(10) unsigned DEFAULT '0' COMMENT '涉及实体数量',PRIMARY KEY (`id`,`log_time`),KEY `aal_idx_log_time` (`log_time`),KEY `aal_idx_operator` (`operator_id`),KEY `aal_idx_target` (`target_type`,`target_id`),KEY `aal_idx_summary` (`action_summary`(100)),KEY `idx_cross_partition` (`log_time`,`action_type`,`operator_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)mysql> 

步骤2:执行分区交换

ALTER TABLE audit_log EXCHANGE PARTITION p2021Q3 WITH TABLE audit_log_archive_2021Q3;

操作记录:

mysql> ALTER TABLE audit_log EXCHANGE PARTITION p2021Q3 WITH TABLE audit_log_archive_2021Q3;
Query OK, 0 rows affected (0.01 sec)mysql>
mysql> SELECT COUNT(*) FROM audit_log_archive_2021Q3;
+----------+
| COUNT(*) |
+----------+
|   740000 |
+----------+
1 row in set (0.00 sec)mysql> 

步骤3:验证后删除原分区

-- 检查交换后分区是否为空
SELECT COUNT(*) FROM audit_log PARTITION (p2021Q3);-- 删除空分区
ALTER TABLE audit_log DROP PARTITION p2021Q3;

操作记录:

mysql> SELECT COUNT(*) FROM audit_log PARTITION (p2021Q3);
+----------+
| COUNT(*) |
+----------+
|        0 |
+----------+
1 row in set, 1 warning (0.01 sec)mysql> 
mysql> ALTER TABLE audit_log DROP PARTITION p2021Q3;
Query OK, 0 rows affected, 1 warning (0.02 sec)mysql>
mysql> SHOW CREATE TABLE audit_log\G
*************************** 1. row ***************************Table: audit_log
Create Table: CREATE TABLE `audit_log` (`id` bigint(20) unsigned NOT NULL,`log_time` datetime NOT NULL COMMENT '日志产生时间',`operator_id` varchar(64) NOT NULL COMMENT '操作人ID (索引)',`operator_name` varchar(128) NOT NULL COMMENT '操作人名称快照',`action_type` varchar(50) NOT NULL COMMENT '操作类型',`target_type` varchar(50) NOT NULL COMMENT '目标类型',`target_id` varchar(64) NOT NULL COMMENT '目标ID',`target_name` varchar(255) NOT NULL COMMENT '目标名称快照',`action_summary` varchar(500) NOT NULL COMMENT '操作摘要描述',`change_count` int(10) unsigned DEFAULT '0' COMMENT '变更项数量',`entity_count` int(10) unsigned DEFAULT '0' COMMENT '涉及实体数量',PRIMARY KEY (`id`,`log_time`),KEY `aal_idx_log_time` (`log_time`),KEY `aal_idx_operator` (`operator_id`),KEY `aal_idx_target` (`target_type`,`target_id`),KEY `aal_idx_summary` (`action_summary`(100)),KEY `idx_cross_partition` (`log_time`,`action_type`,`operator_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
/*!50500 PARTITION BY RANGE  COLUMNS(log_time)
(PARTITION p2021Q4 VALUES LESS THAN ('2022-01-01') ENGINE = MyISAM,PARTITION p2022Q1 VALUES LESS THAN ('2022-04-01') ENGINE = MyISAM,PARTITION p2022Q2 VALUES LESS THAN ('2022-07-01') ENGINE = MyISAM,PARTITION p2022Q3 VALUES LESS THAN ('2022-10-01') ENGINE = MyISAM,PARTITION p2022Q4 VALUES LESS THAN ('2023-01-01') ENGINE = MyISAM,PARTITION p2023Q1 VALUES LESS THAN ('2023-04-01') ENGINE = MyISAM,PARTITION p2023Q2 VALUES LESS THAN ('2023-07-01') ENGINE = MyISAM,PARTITION p2023Q3 VALUES LESS THAN ('2023-10-01') ENGINE = MyISAM,PARTITION p2023Q4 VALUES LESS THAN ('2024-01-01') ENGINE = MyISAM,PARTITION p2024Q1 VALUES LESS THAN ('2024-04-01') ENGINE = MyISAM,PARTITION p2024Q2 VALUES LESS THAN ('2024-07-01') ENGINE = MyISAM,PARTITION p2024Q3 VALUES LESS THAN ('2024-10-01') ENGINE = MyISAM,PARTITION p2024Q4 VALUES LESS THAN ('2025-01-01') ENGINE = MyISAM,PARTITION p2025Q1 VALUES LESS THAN ('2025-04-01') ENGINE = MyISAM,PARTITION p2025Q2 VALUES LESS THAN ('2025-07-01') ENGINE = MyISAM,PARTITION p2025Q3 VALUES LESS THAN ('2025-10-01') ENGINE = MyISAM,PARTITION p_future VALUES LESS THAN (MAXVALUE) ENGINE = MyISAM) */
1 row in set, 1 warning (0.00 sec)mysql> 

将归档的数据迁移到同实例的其他数据库

mysql> RENAME TABLE demo_db.audit_log_archive_2021Q3 TO demo_db_bak.audit_log_archive_2021Q3; 
Query OK, 0 rows affected (0.01 sec)mysql>

当前操作的用户要有这两个库的权限;

如果存储引擎是 InnoDB 的可以对迁移后的数据进行压缩:ALTER TABLE audit_log_archive_2021Q3 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;

迁移到不同的MySQL服务器

# 1. 导出表结构和数据
mysqldump -uroot -p demo_db audit_log_archive_2021Q3 > archive_2021Q3.sql;# 2. 传输文件到目标服务器
scp archive_2021Q3.sql user@remote-server:/tmp/# 3. 在目标服务器导入
mysql -u root -p demo_db_bak < /tmp/archive_2021Q3.sql# 4. 在当前服务器上删除当前归档表
DROP TABLE current_db.audit_log_archive_2021Q3;

若目标服务器使用不同 MySQL 版本,需测试兼容性;

定期添加新分区

如:提前添加下一个季度(2025-09-30 之前添加 2025Q4 分区),或者提前添加下一年的分区(2025-12-31 之前添加 2026Q1,2026Q2,2026Q3,2026Q4)

-- 示例:添加下一季度(2025Q4)的分区
ALTER TABLE audit_log REORGANIZE PARTITION p_future INTO (PARTITION p2025Q4 VALUES LESS THAN ('2026-01-01'),PARTITION p_future VALUES LESS THAN (MAXVALUE)
);

操作记录:

mysql> ALTER TABLE audit_log REORGANIZE PARTITION p_future INTO (->     PARTITION p2025Q4 VALUES LESS THAN ('2026-01-01'),->     PARTITION p_future VALUES LESS THAN (MAXVALUE)-> );
Query OK, 0 rows affected, 1 warning (0.01 sec)mysql> 
mysql> SHOW CREATE TABLE audit_log\G
*************************** 1. row ***************************Table: audit_log
Create Table: CREATE TABLE `audit_log` (`id` bigint(20) unsigned NOT NULL,`log_time` datetime NOT NULL COMMENT '日志产生时间',`operator_id` varchar(64) NOT NULL COMMENT '操作人ID (索引)',`operator_name` varchar(128) NOT NULL COMMENT '操作人名称快照',`action_type` varchar(50) NOT NULL COMMENT '操作类型',`target_type` varchar(50) NOT NULL COMMENT '目标类型',`target_id` varchar(64) NOT NULL COMMENT '目标ID',`target_name` varchar(255) NOT NULL COMMENT '目标名称快照',`action_summary` varchar(500) NOT NULL COMMENT '操作摘要描述',`change_count` int(10) unsigned DEFAULT '0' COMMENT '变更项数量',`entity_count` int(10) unsigned DEFAULT '0' COMMENT '涉及实体数量',PRIMARY KEY (`id`,`log_time`),KEY `aal_idx_log_time` (`log_time`),KEY `aal_idx_operator` (`operator_id`),KEY `aal_idx_target` (`target_type`,`target_id`),KEY `aal_idx_summary` (`action_summary`(100)),KEY `idx_cross_partition` (`log_time`,`action_type`,`operator_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
/*!50500 PARTITION BY RANGE  COLUMNS(log_time)
(PARTITION p2021Q4 VALUES LESS THAN ('2022-01-01') ENGINE = MyISAM,PARTITION p2022Q1 VALUES LESS THAN ('2022-04-01') ENGINE = MyISAM,PARTITION p2022Q2 VALUES LESS THAN ('2022-07-01') ENGINE = MyISAM,PARTITION p2022Q3 VALUES LESS THAN ('2022-10-01') ENGINE = MyISAM,PARTITION p2022Q4 VALUES LESS THAN ('2023-01-01') ENGINE = MyISAM,PARTITION p2023Q1 VALUES LESS THAN ('2023-04-01') ENGINE = MyISAM,PARTITION p2023Q2 VALUES LESS THAN ('2023-07-01') ENGINE = MyISAM,PARTITION p2023Q3 VALUES LESS THAN ('2023-10-01') ENGINE = MyISAM,PARTITION p2023Q4 VALUES LESS THAN ('2024-01-01') ENGINE = MyISAM,PARTITION p2024Q1 VALUES LESS THAN ('2024-04-01') ENGINE = MyISAM,PARTITION p2024Q2 VALUES LESS THAN ('2024-07-01') ENGINE = MyISAM,PARTITION p2024Q3 VALUES LESS THAN ('2024-10-01') ENGINE = MyISAM,PARTITION p2024Q4 VALUES LESS THAN ('2025-01-01') ENGINE = MyISAM,PARTITION p2025Q1 VALUES LESS THAN ('2025-04-01') ENGINE = MyISAM,PARTITION p2025Q2 VALUES LESS THAN ('2025-07-01') ENGINE = MyISAM,PARTITION p2025Q3 VALUES LESS THAN ('2025-10-01') ENGINE = MyISAM,PARTITION p2025Q4 VALUES LESS THAN ('2026-01-01') ENGINE = MyISAM,PARTITION p_future VALUES LESS THAN (MAXVALUE) ENGINE = MyISAM) */
1 row in set, 1 warning (0.00 sec)mysql> 

建议分区表还是在 InnoDB 下使用,将 audit_log 转为 InnoDB

-- 转换表引擎
ALTER TABLE audit_log ENGINE=InnoDB;
-- 转换后需重建所有分区
ALTER TABLE audit_log REBUILD PARTITION ALL;

mysql> ALTER TABLE audit_log ENGINE=InnoDB;
Query OK, 13910004 rows affected (2 min 34.70 sec)mysql> 
mysql> ALTER TABLE audit_log REBUILD PARTITION ALL;
Query OK, 0 rows affected (3 min 50.76 sec)mysql> 
mysql> SHOW CREATE TABLE audit_log\G
*************************** 1. row ***************************Table: audit_log
Create Table: CREATE TABLE `audit_log` (`id` bigint(20) unsigned NOT NULL,`log_time` datetime NOT NULL COMMENT '日志产生时间',`operator_id` varchar(64) NOT NULL COMMENT '操作人ID (索引)',`operator_name` varchar(128) NOT NULL COMMENT '操作人名称快照',`action_type` varchar(50) NOT NULL COMMENT '操作类型',`target_type` varchar(50) NOT NULL COMMENT '目标类型',`target_id` varchar(64) NOT NULL COMMENT '目标ID',`target_name` varchar(255) NOT NULL COMMENT '目标名称快照',`action_summary` varchar(500) NOT NULL COMMENT '操作摘要描述',`change_count` int(10) unsigned DEFAULT '0' COMMENT '变更项数量',`entity_count` int(10) unsigned DEFAULT '0' COMMENT '涉及实体数量',PRIMARY KEY (`id`,`log_time`),KEY `aal_idx_log_time` (`log_time`),KEY `aal_idx_operator` (`operator_id`),KEY `aal_idx_target` (`target_type`,`target_id`),KEY `aal_idx_summary` (`action_summary`(100)),KEY `idx_cross_partition` (`log_time`,`action_type`,`operator_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
/*!50500 PARTITION BY RANGE  COLUMNS(log_time)
(PARTITION p2021Q4 VALUES LESS THAN ('2022-01-01') ENGINE = InnoDB,PARTITION p2022Q1 VALUES LESS THAN ('2022-04-01') ENGINE = InnoDB,PARTITION p2022Q2 VALUES LESS THAN ('2022-07-01') ENGINE = InnoDB,PARTITION p2022Q3 VALUES LESS THAN ('2022-10-01') ENGINE = InnoDB,PARTITION p2022Q4 VALUES LESS THAN ('2023-01-01') ENGINE = InnoDB,PARTITION p2023Q1 VALUES LESS THAN ('2023-04-01') ENGINE = InnoDB,PARTITION p2023Q2 VALUES LESS THAN ('2023-07-01') ENGINE = InnoDB,PARTITION p2023Q3 VALUES LESS THAN ('2023-10-01') ENGINE = InnoDB,PARTITION p2023Q4 VALUES LESS THAN ('2024-01-01') ENGINE = InnoDB,PARTITION p2024Q1 VALUES LESS THAN ('2024-04-01') ENGINE = InnoDB,PARTITION p2024Q2 VALUES LESS THAN ('2024-07-01') ENGINE = InnoDB,PARTITION p2024Q3 VALUES LESS THAN ('2024-10-01') ENGINE = InnoDB,PARTITION p2024Q4 VALUES LESS THAN ('2025-01-01') ENGINE = InnoDB,PARTITION p2025Q1 VALUES LESS THAN ('2025-04-01') ENGINE = InnoDB,PARTITION p2025Q2 VALUES LESS THAN ('2025-07-01') ENGINE = InnoDB,PARTITION p2025Q3 VALUES LESS THAN ('2025-10-01') ENGINE = InnoDB,PARTITION p2025Q4 VALUES LESS THAN ('2026-01-01') ENGINE = InnoDB,PARTITION p_future VALUES LESS THAN (MAXVALUE) ENGINE = InnoDB) */
1 row in set (0.00 sec)mysql> 

http://www.aitangshan.cn/news/574.html

相关文章:

  • 从 “JSON 字段适配噩梦” 到 “Spring Boot 优雅解决方案”,你只差这一篇
  • 【IEEE出版】第四届电力系统与电力工程国际学术会议(PSPE 2025)
  • 题解:P10299 [CCC 2024 S5] Chocolate Bar Partition
  • 关闭Ollama开机启动项
  • MySQL 根据一个表的字段值,更新另一个表的字段
  • DeepCompare文件深度对比软件:智能同步滚动与对比视图管理功能完全指南
  • 书单
  • 2025 款潘通色卡 PS/AI 插件推荐:解锁高效配色新体验
  • Dubbo源码—1.服务发布的主要流程
  • 剑指offer-20、包含min函数的栈
  • CF1456E XOR-ranges 题解
  • QueryCon 2019:osquery的重大转折点 - 技术治理与社区共建
  • 基于Transformer的百万级文本分类技术
  • 详细介绍:网络基础1-11综合实验(eNSP):vlan/DHCP/Web/HTTP/动态PAT/静态NAT
  • Omnissa Horizon Windows OS Optimization Tool 2506 - Windows 系统映像优化工具
  • docker 容器化部署 vLLM 启动大模型
  • App Linking 助力应用场景创新,操作步骤立省 60%
  • ChatGpt 5系列文章1——编码与智能体
  • Cisco Catalyst 9800-CL IOS XE 17.18.1 发布,新增功能简介
  • Cisco Modeling Labs (CML) 2.9.0 - 网络仿真工具
  • Omnissa App Volumes 4, version 2506 - 实时应用程序交付系统
  • Omnissa Dynamic Environment Manager 2506 - 个性化动态 Windows 桌面环境管理
  • AES 加密模式演进:从 ECB、CBC 到 GCM 的 C# 深度实践
  • Cisco Catalyst 9800 WLC IOS XE 17.18.1 发布,新增功能简介
  • 详细介绍:python办自动化--读取邮箱中特定的邮件,并下载特定的附件
  • 微软开源的 MCP 教程「GitHub 热点速览」
  • 题解:qoj10322 Matching Query
  • ZR Summer 2025 CD ACM暨 ZR Summer 2025 C 游记
  • flutter flutter_inappwebview插件里js上传调用相机和图库碰到的问题
  • ruoyi-cloud微服务docker部署