
# 优化阶段拦截写事务
#### 功能介绍
在MySQL中，单个事务如果涉及大规模数据操作（如插入、更新或删除数百万行），会长时间持有锁并大量消耗I/O资源，严重影响后续业务的正常执行。用户往往不得不强制终止会话，触发回滚操作。回滚过程通常与原事务执行时间相当，期间仍会占用数据库锁和I/O资源，进一步阻塞其他工作负载。
为避免此类性能瓶颈，华为云RDS for MySQL在此类大规模写入事务执行前进行拦截，防止因数据变更过大导致系统整体性能下降，并及时提醒客户关注相关风险，从而保障数据库的稳定运行和服务的高可用性。此外，该特性开启后对数据库写性能会有轻微影响。
#### 前提条件
- 对于RDS for MySQL 5.7，要求内核版本大于等于5.7.44.241207。
- 对于RDS for MySQL 8.0，要求内核版本大于等于8.0.41.250900。
 
#### 配置参数
表1参数说明 
| 参数名称                              | 是否动态参数 | 数据类型    | 取值范围                       | 描述                                                                                                                                                                          |
|:---|:---|:---|:---|:---|
| rds_max_data_mutation_num_per_dml | 是      | integer | \[0, 10000-9999999999999\] | INSERT/UPDATE/DELETE可影响的行数的最大值，超过了该配置值，则提前拦截报错。 该参数是会话级参数。 默认值为0，表示不拦截。可配置0，以及\[10000, 9999999999999\]。          |
| rds_data_mutation_counting_mode   | 是      | enum    | \[PRECISE, ESTIMATED\]     | INSERT/UPDATE/DELETE影响的行数评估模式。在无法获取结果集行数的相对精确值时，ESTIMATED模式下依赖优化器估算值，PRECISE模式下则不做拦截。 该参数是会话级参数。 默认值为ESTIMATED。 |
   
#### 使用限制
当前仅支持语句级的限制，以下语句在执行前会提前风险识别并拦截：
表2使用限制 
| SQL语句                   | 约束条件                                                                                                                                                                                                                                  |
|:---|:---|
| INSERT ... VALUES       | 仅对SQL语句VALUES增加的行数进行限制，非实际插入的行数。                                                                                                                                                                                                      |
| REPLACE ... VALUES      | 仅对SQL语句VALUES增加的行数进行限制，非实际插入的行数。                                                                                                                                                                                                      |
| INSERT ... SELECT       | - 仅对优化器估算的SELECT语句输出行数进行限制。  - SELECT语句结果未做缓存处理，且对应的执行计划包含多表JOIN时不做限制。         |
| REPLACE ... SELECT      | - 仅对优化器估算的SELECT语句输出行数进行限制。  - SELECT语句结果未做缓存处理，且对应的执行计划包含多表JOIN时不做限制。   |
| CREATE TABLE ... SELECT | - 仅对优化器估算的SELECT语句输出行数进行限制。  - SELECT语句结果未做缓存处理，且对应的执行计划包含多表JOIN时不做限制。           |
| UPDATE TABLE ...        | - 仅支持单表。  - 仅对优化器估算的整表或者where条件过滤后输出行数进行限制。                          |
| DELETE FROM TABLE ...   | - 仅支持单表。  - 仅对优化器估算的整表或者where条件过滤后输出行数进行限制。                                   |
   
由于SQL尚未实际执行，系统只能依赖优化器的估算值进行拦截判断，因此无法做到绝对精确。然而，在以下几类场景中，可以获取到相对准确的行数信息：
- 对表执行无限制条件的INSERT ... VALUES操作。
- INSERT/REPLACE ... SELECT或CREATE TABLE ... SELECT语句，其中 SELECT 的结果在插入前需要缓存（比如物化临时表、SORT缓存等）。
- UPDATE/DELETE语句在执行前需要缓存所有待更新行的ROW ID（比如临时文件、SORT 缓存等）。
- 单表查询可直接使用索引访问（如ref或者range），无额外过滤等操作。
在上述场景中，无论当前配置为PRECISE模式还是ESTIMATED模式，系统都会基于获取到的行数来决定是否进行拦截处理。
#### 使用示例
以下是优化阶段拦截写事务的使用示例：
1. 表结构。
   ```
   mysql> SHOW CREATE TABLE t1\G
   *************************** 1. row ***************************
          Table: t1
   Create Table: CREATE TABLE `t1` (
     `id` int NOT NULL AUTO_INCREMENT,
     `category` int NOT NULL,
     `value` int NOT NULL,
     `flag` enum('active','inactive') DEFAULT 'active',
     `created_at` timestamp NOT NULL DEFAULT '2025-05-05 01:01:01',
     PRIMARY KEY (`id`),
     KEY `idx_category` (`category`),
     KEY `idx_value` (`value`),
     KEY `idx_flag` (`flag`),
     KEY `idx_category_value` (`category`,`value`)
   ) ENGINE=InnoDB AUTO_INCREMENT=120109 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
   1 row in set (0.00 sec)
    
   mysql> CREATE TABLE t2 LIKE t1;
   Query OK, 0 rows affected (0.15 sec)
   ```
   
2. INSERT ... SELECT语句拦截示例。
   ```
   mysql> EXPLAIN FORMAT=TREE INSERT INTO t2 SELECT * FROM t1 WHERE category=1;
   +------------------------------------------------------------------------------------------------------+
   | EXPLAIN                                                                                              |
   +------------------------------------------------------------------------------------------------------+
   | -> Insert into t2
       -> Index lookup on t1 using idx_category (category=1)  (cost=6276 rows=60112)
    |
   +------------------------------------------------------------------------------------------------------+
   1 row in set (0.02 sec)
    
   mysql> SET rds_max_data_mutation_num_per_dml=60111;
   Query OK, 0 rows affected (0.00 sec)
    
   # 单表走ref索引无过滤条件，估算值超过门限，'ESTIMATED'模式下会拦截
   mysql> SET rds_data_mutation_counting_mode='ESTIMATED';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> INSERT INTO t2 SELECT * FROM t1 WHERE category=1;
   ERROR 4167 (HY000): The number [60112] of data mutations in the SQL statement has exceeded the maximum limit. Please check the configuration parameter 'rds_max_data_mutation_num_per_dml'.
   mysql>
    
   # 单表走ref索引无过滤条件，估算值超过门限，'PRECISE'模式下会拦截
   mysql> SET rds_data_mutation_counting_mode='PRECISE';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> INSERT INTO t2 SELECT * FROM t1 WHERE category=1;
   ERROR 4167 (HY000): The number [60112] of data mutations in the SQL statement has exceeded the maximum limit. Please check the configuration parameter 'rds_max_data_mutation_num_per_dml'.
   mysql>
    
   mysql> EXPLAIN FORMAT=TREE INSERT INTO t2 SELECT * FROM t1 WHERE category=1 AND flag='active';
   +----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
   | EXPLAIN                                                                                                                                                              |
   +----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
   | -> Insert into t2
       -> Filter: (t1.flag = 'active')  (cost=3270 rows=30056)
           -> Index lookup on t1 using idx_category (category=1)  (cost=3270 rows=60112)
    |
   +----------------------------------------------------------------------------------------------------------------------------------------------------------------------+
   1 row in set (0.00 sec)
    
   mysql> SET rds_max_data_mutation_num_per_dml=30055;
   Query OK, 0 rows affected (0.00 sec)
    
   # 存在无法走索引的过滤条件，且估算值超过门限，'ESTIMATED'模式下会拦截
   mysql> SET rds_data_mutation_counting_mode='ESTIMATED';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> INSERT INTO t2 SELECT * FROM t1 WHERE category=1 AND flag='active';
   ERROR 4167 (HY000): The number [30056] of data mutations in the SQL statement has exceeded the maximum limit. Please check the configuration parameter 'rds_max_data_mutation_num_per_dml'.
   mysql>
   mysql> SET rds_data_mutation_counting_mode='PRECISE';
   Query OK, 0 rows affected (0.00 sec)
    
   # 存在无法走索引的过滤条件，且估算值超过门限，'PRECISE'模式下不拦截
   mysql> INSERT INTO t2 SELECT * FROM t1 WHERE category=1 AND flag='active';
   Query OK, 32275 rows affected (37.11 sec)
   Records: 32275  Duplicates: 0  Warnings: 0
    
   mysql>
   ```
   
3. UPDATE语句拦截示例。
   ```
   mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE category=1;
   +--------------------------------------------------------------------------------+
   | EXPLAIN                                                                        |
   +--------------------------------------------------------------------------------+
   | -> Index lookup on t1 using idx_category (category=1)  (cost=4710 rows=44448)
    |
   +--------------------------------------------------------------------------------+
   1 row in set (0.00 sec)
    
   mysql> SET rds_max_data_mutation_num_per_dml=44447;
   Query OK, 0 rows affected (0.00 sec)
    
   # 单表走ref索引无过滤条件，估算值超过门限，'ESTIMATED'模式下会拦截
   mysql> SET rds_data_mutation_counting_mode='ESTIMATED';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> UPDATE t1 SET value=value+100 WHERE category=1;
   ERROR 4167 (HY000): The number [44448] of data mutations in the SQL statement has exceeded the maximum limit. Please check the configuration parameter 'rds_max_data_mutation_num_per_dml'.
   mysql>
    
   # 单表走ref索引无过滤条件，估算值超过门限，'PRECISE'模式下会拦截
   mysql> SET rds_data_mutation_counting_mode='PRECISE';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> UPDATE t1 SET value=value+100 WHERE category=1;
   ERROR 4167 (HY000): The number [44448] of data mutations in the SQL statement has exceeded the maximum limit. Please check the configuration parameter 'rds_max_data_mutation_num_per_dml'.
   mysql>
   mysql>  EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE category=1 AND flag='active';
   +--------------------------------------------------------------------------------------------------------------------------------------------+
   | EXPLAIN                                                                                                                                    |
   +--------------------------------------------------------------------------------------------------------------------------------------------+
   | -> Filter: (t1.flag = 'active')  (cost=2487 rows=22224)
       -> Index lookup on t1 using idx_category (category=1)  (cost=2487 rows=44448)
    |
   +--------------------------------------------------------------------------------------------------------------------------------------------+
   1 row in set (0.01 sec)
    
   mysql>
   mysql> SET rds_max_data_mutation_num_per_dml=22223;
   Query OK, 0 rows affected (0.00 sec)
    
   mysql>
    
   # 存在无法走索引的过滤条件，且估算值超过门限，'ESTIMATED'模式下会拦截
   mysql> SET rds_data_mutation_counting_mode='ESTIMATED';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> UPDATE t1 SET value=value+100 WHERE category=1 AND flag='active';
   ERROR 4167 (HY000): The number [22224] of data mutations in the SQL statement has exceeded the maximum limit. Please check the configuration parameter 'rds_max_data_mutation_num_per_dml'.
   mysql>
    
   # 存在无法走索引的过滤条件，且估算值超过门限，'PRECISE'模式下不拦截
   mysql> SET rds_data_mutation_counting_mode='PRECISE';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> UPDATE t1 SET value=value+100 WHERE category=1 AND flag='active';
   Query OK, 32275 rows affected (43.55 sec)
   Rows matched: 32275  Changed: 32275  Warnings: 0
   ```
   
4. DELETE语句拦截示例。
   ```
   mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE category=2;
   +--------------------------------------------------------------------------------+
   | EXPLAIN                                                                        |
   +--------------------------------------------------------------------------------+
   | -> Index lookup on t1 using idx_category (category=2)  (cost=6276 rows=60112)
    |
   +--------------------------------------------------------------------------------+
   1 row in set (0.00 sec)
    
   mysql>
   mysql> SET rds_max_data_mutation_num_per_dml=60111;
   Query OK, 0 rows affected (0.00 sec)
    
   # 单表走ref索引无过滤条件，估算值超过门限，'ESTIMATED'模式下会拦截
   mysql> SET rds_data_mutation_counting_mode='ESTIMATED';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> DELETE FROM t1 WHERE category=2;
   ERROR 4167 (HY000): The number [60112] of data mutations in the SQL statement has exceeded the maximum limit. Please check the configuration parameter 'rds_max_data_mutation_num_per_dml'.
   mysql>
    
   # 单表走ref索引无过滤条件，估算值超过门限，'PRECISE'模式下会拦截
   mysql> SET rds_data_mutation_counting_mode='PRECISE';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> DELETE FROM t1 WHERE category=2;
   ERROR 4167 (HY000): The number [60112] of data mutations in the SQL statement has exceeded the maximum limit. Please check the configuration parameter 'rds_max_data_mutation_num_per_dml'.
   mysql>
   mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE category=2 AND flag='active';
   +--------------------------------------------------------------------------------------------------------------------------------------------+
   | EXPLAIN                                                                                                                                    |
   +--------------------------------------------------------------------------------------------------------------------------------------------+
   | -> Filter: (t1.flag = 'active')  (cost=3270 rows=30056)
       -> Index lookup on t1 using idx_category (category=2)  (cost=3270 rows=60112)
    |
   +--------------------------------------------------------------------------------------------------------------------------------------------+
   1 row in set (0.12 sec)
    
   mysql> SET rds_max_data_mutation_num_per_dml=30055;
   Query OK, 0 rows affected (0.00 sec)
    
   # 存在无法走索引的过滤条件，且估算值超过门限，'ESTIMATED'模式下会拦截
   mysql> SET rds_data_mutation_counting_mode='ESTIMATED';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> DELETE FROM t1 WHERE category=2 AND flag='active';
   ERROR 4167 (HY000): The number [30056] of data mutations in the SQL statement has exceeded the maximum limit. Please check the configuration parameter 'rds_max_data_mutation_num_per_dml'.
   mysql>
    
   # 存在无法走索引的过滤条件，且估算值超过门限，'PRECISE'模式下不拦截
   mysql> SET rds_data_mutation_counting_mode='PRECISE';
   Query OK, 0 rows affected (0.00 sec)
    
   mysql> DELETE FROM t1 WHERE category=2 AND flag='active';
   Query OK, 32040 rows affected (41.72 sec)
   ```
   
 
