首批通过分布式安全可靠测评,为关键业务系统打造
last_insert_id() 表达式无法预计算导致主键查询走了全表扫描
更新时间:2026-05-26 09:46
问题现象
当 SQL 中的主键列或者索引列上的查询条件中含有 last_insert_id() 表达式时,由于 OceanBase 没有预计算 last_insert_id() 表达式,导致主键或索引上抽不出 query range,进一步的引起了全表扫描。举例如下:
OceanBase(root@test)>create table t1(c1 int primary key, c2 int);
Query OK, 0 rows affected (0.41 sec)
--预期计划应该是一个TABLE GET
OceanBase(root@test)>explain select * from t1 where c1 = last_insert_id();
+------------------------------------------------------------------------------------+
| Query Plan |
+------------------------------------------------------------------------------------+
| ========================================== |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| ------------------------------------------ |
| |0 |TABLE SCAN|t1 |1 |2 | |
| ========================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([t1.c1], [t1.c2]), filter([t1.c1 = last_insert_id()]), rowset=256 |
| access([t1.c1], [t1.c2]), partitions(p0) |
| is_index_back=false, is_global_index=false, filter_before_indexback[false], |
| range_key([t1.c1]), range(MIN ; MAX)always true |
+------------------------------------------------------------------------------------+
关键诊断信息
触发条件
SQL 中的主键列或者索引列上的查询条件中含有 last_insert_id() 表达式。
问题原因
由于 last_insert_id() 实现的历史原因,这个表达式过去没有被当做一个常量表达式来对待,即使它的值在一次 query 执行中预期是不变的。预期不变是因为存在 last_insert_id(expr) 的用法,这种带参数执行的当时会影响 last_insert_id() 返回的结果。参考 MySQL 的官方文档可知,当一条 SQL 中同时存在 last_insert_id() 和 last_insert_id(expr) 时,last_insert_id() 的表现是未定义,因此在实现上我们是可以认为 last_insert_id() 的值在一次 query 执行中是不变的,也就是可以用来抽取 range range。
问题的风险及影响
这个问题可能会导致本来可以走主键扫描或者索引键扫描的场景错误的走到了全表扫描,导致 SQL 的执行性能大大下降。
影响租户
影响 OceanBase 数据库中的 MySQL 租户,对于 SYS 租户和 Oracle 租户无影响。
影响版本
OceanBase 数据库企业版 V2.2.77 GA(oceanbase-2.2.77-20210508211731)及之后版本、V3.1.2 GA(oceanbase-3.1.2-20210618150922)及之后版本、V3.2.3 GA(oceanbase-3.2.3.0-20220418212020)及之后版本、V3.2.4 GA(oceanbase-3.2.4.0-100000072022102819)及之后版本、V4.1.0 GA(oceanbase-4.1.0.0-100001122023040322)及之后版本。
解决方法
升级至问题已修复版本。目前已修复的版本包括 OceanBase 数据库企业版 V2.2.77 BP17(oceanbase-2.2.77-117000112023051914)及之后版本、V3.2.3 BP7 Hotfix2(oceanbase-3.2.3.3-107020062023022810)及之后版本、V3.2.3 BP7 Hotfix3(oceanbase-3.2.3.3-107030052023031712)及之后版本、V3.2.3 BP7 Hotfix4(oceanbase-3.2.3.3-107040032023032910)及之后版本、V3.2.3 BP7 Hotfix5(oceanbase-3.2.3.3-107050022023040817)及之后版本、V3.2.3 BP7 Hotfix6(oceanbase-3.2.3.3-107060012023041113)及之后版本、V3.2.3 BP8(oceanbase-3.2.3.3-108000062023041511)及之后版本、V3.2.4 BP2(oceanbase-3.2.4.2-102000042023022717)及之后版本、V4.1.0 BP3(oceanbase-4.1.0.2-103000072023081111)及之后版本。
规避方式
如果条件允许可以通过改写 SQL 的方式绕过。利用用户变量将一条 SQ L拆成两条直行。示例如下。
-- 利用用户变量将一条 SQL 拆成二条直行
select * from t1 where pk = last_insert_id();
=>
set @insert_id_var = last_insert_id();
select * from t1 where pk = @insert_id_var;