首批通过分布式安全可靠测评,为关键业务系统打造
分页保序开启,limit 条件不能下压到基表
更新时间:2026-05-14 09:21
对 Mysql 5.6 迁移过来的SQL默认只会生成 Nested Loop Join 计划,且 NLJ 是保序计划,但是 OceanBase 可能会生成 Hash Join 等非保序的计划。 针对这种情况,OceanBase 提供 _preserve_order_for_pagination 分页保序参数,通过修改系统配置项或者 Hint 的方式,打开或者关闭分页保序。需要注意的是,打开分页保序功能后,可能部分查询会出现性能回退,因为引入了额外的排序计算,查询执行性能可能会下降。
本文介绍分页保序开启后,可能导致 limit xx 条件不能下压到基表,从而导致执行性能慢的一种场景及优化手段。
详细说明
最小化测试 case。
obclient [new]> create table t1(c1 int, c2 int, c3 int, c4 int, c5 int, c6 varchar(20), primary key(c1));
Query OK, 0 rows affected (0.396 sec)
obclient [new]> insert into t1 select row_number() over(), row_number() over(), row_number() over(), row_number() over(),row_number() over(), row_number() over() from TABLE(GENERATOR(100000));
Query OK, 100000 rows affected (1.233 sec)
Records: 100000 Duplicates: 0 Warnings: 0
obclient [new]> create table t2 (c1 int, c2 int, c3 int, c4 varchar(20));
Query OK, 0 rows affected (0.348 sec)
obclient [new]> insert into t2 select row_number() over(), row_number() over(), row_number() over(), row_number() over() from TABLE(GENERATOR(40000));
Query OK, 40000 rows affected (0.894 sec)
Records: 40000 Duplicates: 0 Warnings: 0
obclient [new]> create table t3 (c1 int, c2 int, c3 int, c4 varchar(20));
Query OK, 0 rows affected (0.559 sec)
obclient [new]> insert into t3 select row_number() over(), row_number() over(),row_number() over(), row_number() over() from TABLE(GENERATOR(20000));
Query OK, 20000 rows affected (0.590 sec)
Records: 20000 Duplicates: 0 Warnings: 0
原始 sql 中,可以看到按照 t1.c1 列逆序排列,并输出 10 条数据。
select t2.c2,t2.c3,t1.c5,t1.c4,t3.c2,t3.c4
from t1
left join t2 on t1.c2 = t2.c3
left join t3 on t3.c3 is not null
and t3.c3 = t1.c5
where t1.c2 = 200
order by t1.c1 desc
limit 0, 10;
在没有开启分页保序的场景下,默认生成的计划可以看到 limit 10 下压到 t1 表 5 号算子。

当打开分页保序后,t1 表不能走 limit 10 下压的计划,需要通过表上的过滤条件减少扫表的数据量,性能差于下压计划。

对应优化器 trace 中可以看到,开启分页保序后,优化器默认将投影列中的字段补到原始 order by 列 (t1.c1) 后面。所以为了保证分页保序, 默认将投影列按序作为排序列,这样导致了 order by t1.c1 的 limit 条件不能下压到 t1 基表了。
select
/*+ OPT_PARAM('PRESERVE_ORDER_FOR_PAGINATION' 'true') */
`new`.`t2`.`c2`,
`new`.`t2`.`c3`,
`new`.`t1`.`c5`,
`new`.`t1`.`c4`,
`new`.`t3`.`c2`,
`new`.`t3`.`c4`
from (
(
`new`.`t1`
left join `new`.`t2` on (`new`.`t1`.`c2` = `new`.`t2`.`c3`)
)
left join `new`.`t3` on (`new`.`t3`.`c3` is not null)
and (`new`.`t3`.`c3` = `new`.`t1`.`c5`)
)
where (`new`.`t1`.`c2` = :0)
order by `new`.`t1`.`c1` desc,
1,
2,
3,
4,
5,
6
limit :1, :2
可以通过手动改写 SQL, 将 limit 条件强制下压到 t1 基表进行优化。
改写后的 SQL 如下:
select
/*+opt_param('preserve_order_for_pagination','true')*/ t2.c2,t2.c3,t1.c5,t1.c4,t3.c2,t3.c4
from (
select *
from t1
where t1.c2 = 200
order by t1.c1 desc
limit 0, 10
) t1
left join t2 on t1.c2 = t2.c3
left join t3 on t3.c3 is not null
and t3.c3 = t1.c5
order by t1.c1 desc
limit 0, 10;
对应的计划中,可以看到 limit 条件下压到了 t1 表。

适用版本
OceanBase 数据库 V4.x 版本。