OceanBase 数据库中的 TRUNCATE 语句会先 DROP 该表所有分区,再为其重新创建新的分区。这个过程物理数据不做改动,但会变更表 ID。后续原表 ID 的实体会执行 GC(Garbage Collection,回收处理机制) 流程,留下新 table_id 的实体继续提供读写服务。
OceanBase 数据库在执行 TRUNCATE 时不会对表加锁,因此会出现一个事务执行过程中,新旧表 ID 对应的数据实体分别写了该事务的一部分数据,后续原表 ID 的实体执行了 GC,用户查询该事务的修改时,只查询到一部分数据,从而出现正确性问题。
有关 OceanBase 的回收处理机制,请参见 OceanBase 数据库的 GC 回收处理机制。
问题复现
在 OceanBase 数据库中开启两个会话 S1 和 S2。
在 S1 中执行以下 SQL 语句,向 fruit 表中插入数据。
obclient> BEGIN; obclient> INSERT INTO fruit VALUES (1,1);在 S2 中对 fruit 表执行 TRUNCATE。
obclient> TRUNCATE TABLE fruit;在 S1 中执行以下 SQL 语句,向 fruit 表中插入第二行数据,并提交事务。
obclient> INSERT INTO fruit VALUES (2,2); obclient> COMMIT;在 S2 中查询 fruit 表上的事务。
obclient> SELECT * FROM fruit; +------+------+ | c1 | c2 | +------+------+ | 2 | 2 | +------+------+ 1 row in set (0.01 sec) obclient> SHOW RECYCLEBIN; +---------------------------------------+---------------+-------+----------------------------+ | OBJECT_NAME | ORIGINAL_NAME | TYPE | CREATETIME | +---------------------------------------+---------------+-------+----------------------------+ | __recycle_$_48200632_1607431261749248 | fruit | TABLE | 2020-12-18 19:31:16.723301 | +---------------------------------------+---------------+-------+----------------------------+在 S2 中闪回 fruit 表上的修改,将闪回后的表重命名为 fruit_2。
obclient> FLASHBACK TABLE fruit TO BEFORE DROP RENAME TO fruit_2; obclient> SELECT * FROM fruit_2; +------+------+ | c1 | c2 | +------+------+ | 1 | 1 | +------+------+ 1 row in set (0.01 sec)
由此可见,S1 中一个事务前后的数据分别插入了不同的表。这是由于执行 TRUNCATE TABLE 语句过程中,如果并发执行事务,由于 DDL 语句与 DML 语句不互相锁,会导致事务有一半会在前一个对象上。
说明
虽然 OceanBase 数据库在并发事务中 DDL 语句与 DML 语句不会互相锁定,但同一个会话中的多条语句仍满足因果序。
运维建议
可以通过以下两种方式之一规避该问题。
在执行
TRUNCATE TABLE语句的过程中,为了避免遇到上述问题,需要确保TRUNCATE之前已经没有事务存在。可以使用以下 SQL 查询是否存在事务,其中
table_id为待执行TRUNCATE语句的表 ID。如果查询结果为空集,则表示没有已存在的事务。obclient> SELECT count(1) FROM __all_virtual_trans_stat WHERE 'partition' LIKE '%table_id%';关闭
ob_enable_truncate_flashback变量。ob_enable_truncate_flashback变量用于控制是否启用表截断的闪回,避免在执行 TRUNCATE TABLE 的表上进行闪回操作 。有关该系统变量的详细信息,请参见《OceanBase 数据库 参考指南》中的 系统变量 章节。obclient> SET GLOBAL ob_enable_truncate_flashback=0;