@@ -9,11 +9,22 @@ summary: 以事务的原子性和隔离性为代价,将 DML 语句拆成多个
9
9
10
10
非事务 DML 语句是将一个普通 DML 语句拆成多个 SQL 语句(即多个 batch)执行,以牺牲事务的原子性和隔离性为代价,增强批量数据处理场景下的性能和易用性。
11
11
12
- 非事务 DML 语句包括 ` INSERT ` 、` UPDATE ` 和 ` DELETE ` ,TiDB 目前只支持非事务 ` DELETE ` 语句,详细的语法介绍见 [ ` BATCH ` ] ( /sql-statements/sql-statement-batch.md ) 。
12
+ 通常,对于消耗内存过多的大事务,你需要在应用中拆分 SQL 语句以绕过事务大小限制。非事务 DML 语句将这一过程集成到 TiDB 内核中,实现等价的效果。非事务 DML 语句的执行效果可以通过拆分 SQL 语句的结果来理解,` DRY RUN ` 语法提供了预览拆分后语句的功能。
13
+
14
+ 非事务 DML 语句包括:
15
+
16
+ - ` INSERT INTO ... SELECT `
17
+ - ` REPLACE INTO .. SELECT `
18
+ - ` UPDATE `
19
+ - ` DELETE `
20
+
21
+ 详细的语法介绍见 [ ` BATCH ` ] ( /sql-statements/sql-statement-batch.md ) 。
13
22
14
23
> ** 注意:**
15
24
>
16
- > 非事务 DML 语句不保证该语句的原子性和隔离性,不能认为它和原始 DML 语句等价。
25
+ > - 非事务 DML 语句不保证该语句的原子性和隔离性,不能认为它和原始 DML 语句等价。
26
+ > - 在任意 DML 语句改写为非事务 DML 语句后,不应假设其行为与原来一致。
27
+ > - 使用非事务 DML 前需要分析其拆分后的语句是否会互相影响。
17
28
18
29
## 使用场景
19
30
@@ -31,8 +42,27 @@ summary: 以事务的原子性和隔离性为代价,将 DML 语句拆成多个
31
42
32
43
- 确保该语句不需要原子性,即允许执行结果中,一部分行被修改,而一部分行没有被修改。
33
44
- 确保该语句具有幂等性,或是做好准备根据错误信息对部分数据重试。如果系统变量 ` tidb_redact_log = 1 ` 且 ` tidb_nontransactional_ignore_error = 1 ` ,则该语句必须是幂等的。否则语句部分失败时,无法准确定位失败的部分。
34
- - 确保该语句将要操作的数据没有其它并发的写入,即不被其它语句同时更新。否则可能出现漏删、多删等非预期的现象 。
45
+ - 确保该语句将要操作的数据没有其它并发的写入,即不被其它语句同时更新。否则可能出现漏写、多写、重复修改同一行等非预期的现象 。
35
46
- 确保该语句不会修改语句自身会读取的内容,否则后续的 batch 读到之前 batch 写入的内容,容易引起非预期的情况。
47
+ - 在使用非事务 ` INSERT INTO ... SELECT ` 处理同一张表时,尽量不要在插入时修改拆分列,否则可能因为多个 batch 读取到同一行,导致重复插入:
48
+ - 不推荐使用 ` BATCH ON test.t.id LIMIT 10000 INSERT INTO t SELECT id+1, value FROM t; `
49
+ - 推荐使用 ` BATCH ON test.t.id LIMIT 10000 INSERT INTO t SELECT id, value FROM t; `
50
+ - 当 ` id ` 列具有 ` AUTO_INCREMENT ` 属性时,推荐使用 ` BATCH ON test.t.id LIMIT 10000 INSERT INTO t(value) SELECT value FROM t; `
51
+ - 在使用非事务 ` UPDATE ` 、` INSERT ... ON DUPLICATE KEY UPDATE ` 、` REPLACE INTO ` 时,拆分列不应该在语句中更新:
52
+ - 例如,对于一条非事务 ` UPDATE ` 语句,拆分后的 SQL 依次执行,前一 batch 的修改提交后被后一 batch 读到,导致同一行数据被多次修改。
53
+ - 这类语句不支持 ` BATCH ON test.t.id LIMIT 10000 UPDATE t SET test.t.id = test.t.id-1; `
54
+ - 不推荐使用 ` BATCH ON test.t.id LIMIT 1 INSERT INTO t SELECT id+1, value FROM t ON DUPLICATE KEY UPDATE id = id + 1; `
55
+ - 拆分列也不应该用于 Join key。例如,下面示例将拆分列 ` test.t.id ` 作为 Join key,导致一个非事务 ` UPDATE ` 语句多次更新同一行:
56
+
57
+ ``` sql
58
+ CREATE TABLE t (id int , v int , key(id));
59
+ CREATE TABLE t2 (id int , v int , key(id));
60
+ INSERT INTO t VALUES (1 , 1 ), (2 , 2 ), (3 , 3 );
61
+ INSERT INTO t2 VALUES (1 , 1 ), (2 , 2 ), (4 , 4 );
62
+ BATCH ON test .t .id LIMIT 1 UPDATE t JOIN t2 ON t .id = t2 .id SET t2 .id = t2 .id + 1 ;
63
+ SELECT * FROM t2; -- (4, 1) (4, 2) (4, 4)
64
+ ```
65
+
36
66
- 确认该语句满足[使用限制](# 使用限制)。
37
67
- 不建议在该 DML 语句将要读写的表上同时进行并发的 DDL 操作。
38
68
@@ -104,6 +134,35 @@ SELECT * FROM t;
104
134
1 row in set
105
135
` ` `
106
136
137
+ 以下示例说明多表 join 的使用方法。首先创建表 ` t2` 并插入数据。
138
+
139
+ ` ` ` sql
140
+ CREATE TABLE t2(id int, v int, key(id));
141
+ INSERT INTO t2 VALUES (1,1), (3,3), (5,5);
142
+ ` ` `
143
+
144
+ 然后进行涉及多表 join 的更新(表 ` t` 和 ` t2` )。需要注意的是,指定拆分列时需要完整的数据库名、表名和列名(` test.t._tidb_rowid` )。
145
+
146
+ ` ` ` sql
147
+ BATCH ON test.t._tidb_rowid LIMIT 1 UPDATE t JOIN t2 ON t.id = t2.id SET t2.id = t2.id+1;
148
+ ` ` `
149
+
150
+ 查看更新后表的数据:
151
+
152
+ ` ` ` sql
153
+ SELECT * FROM t2;
154
+ ` ` `
155
+
156
+ ` ` ` sql
157
+ +----+---+
158
+ | id | v |
159
+ +----+---+
160
+ | 1 | 1 |
161
+ | 3 | 3 |
162
+ | 6 | 5 |
163
+ +----+---+
164
+ ` ` `
165
+
107
166
# ## 查看非事务 DML 语句的执行进度
108
167
109
168
非事务 DML 语句执行过程中,可以通过 ` SHOW PROCESSLIST` 查看执行进度,返回结果中的 ` Time` 表示当前 batch 执行的耗时。日志、慢日志等也会记录每个拆分后的语句在整个非事务 DML 语句中的进度。例如:
@@ -183,16 +242,21 @@ BATCH ON id LIMIT 2 DELETE /*+ USE_INDEX(t)*/ FROM t where v < 6;
183
242
建议按照以下步骤执行非事务 DML 语句:
184
243
185
244
1 . 选择合适的[划分列](# 参数说明)。建议使用整数或字符串类型。
186
- 2 . (可选)在非事务 DML 语句中添加 ` DRY RUN QUERY ` ,手动执行查询,确认 DML 语句影响的数据范围是否大体正确。
187
- 3 . (可选)在非事务 DML 语句中添加 ` DRY RUN ` ,手动执行查询,检查拆分后的语句和执行计划。需要关注索引选择效率。
245
+ 2 . 在非事务 DML 语句中添加 ` DRY RUN QUERY` ,手动执行查询,确认 DML 语句影响的数据范围是否大体正确。
246
+ 3 . 在非事务 DML 语句中添加 ` DRY RUN` ,手动执行查询,检查拆分后的语句和执行计划。需要关注:
247
+
248
+ - 一条拆分后的语句是否有可能读到之前的语句执行写入的结果,否则容易造成异常现象。
249
+ - 索引选择效率。
250
+ - 由 TiDB 自动选择的拆分列是否可能会被修改。
251
+
188
252
4 . 执行非事务 DML 语句。
189
253
5 . 如果报错,从报错信息或日志中获取具体失败的数据范围,进行重试或手动处理。
190
254
191
255
# # 参数说明
192
256
193
257
| 参数 | 说明 | 默认值 | 是否必填 | 建议值 |
194
258
| :-- | :-- | :-- | :-- | :-- |
195
- | 划分列 | 用于划分 batch 的列,例如以上非事务 DML 语句 ` BATCH ON id LIMIT 2 DELETE FROM t WHERE v < 6 ` 中的 ` id ` 列 | TiDB 尝试自动选择 | 否 | 选择可以最高效地满足 ` WHERE ` 条件的列 |
259
+ | 划分列 | 用于划分 batch 的列,例如以上非事务 DML 语句 ` BATCH ON id LIMIT 2 DELETE FROM t WHERE v < 6` 中的 ` id` 列 | TiDB 尝试自动选择(不建议) | 否 | 选择可以最高效地满足 ` WHERE` 条件的列 |
196
260
| Batch size | 用于控制每个 batch 的大小,batch 即 DML 操作拆分成的 SQL 语句个数,例如以上非事务 DML 语句 ` BATCH ON id LIMIT 2 DELETE FROM t WHERE v < 6` 中的 ` LIMIT 2` 。batch 数量越多,batch size 越小 | N/ A | 是 | 1000 ~1000000 ,过小和过大都会导致性能下降 |
197
261
198
262
# ## 划分列的选择
@@ -217,8 +281,8 @@ BATCH ON id LIMIT 2 DELETE /*+ USE_INDEX(t)*/ FROM t where v < 6;
217
281
218
282
非事务 DML 语句的硬性限制,不满足这些条件时 TiDB 会报错。
219
283
220
- - 只可对单表进行操作,暂不支持多表连接。
221
284
- DML 语句不能包含 ` ORDER BY` 或 ` LIMIT` 字句。
285
+ - 不支持子查询或集合操作。
222
286
- 用于拆分的列必须被索引。该索引可以是单列的索引,或是一个联合索引的第一列。
223
287
- 必须在 [` autocommit` ](/ system- variables .md # autocommit) 模式中使用。
224
288
- 不能在开启了 batch- dml 时使用。
0 commit comments