openGauss 运维能力之 SQLPatch 解密
对于相同的查询语句而言,不同的查询计划带来的性能差异可能达到几个数量级的差异,其中较为显著的包括:是否命中索引、查询路径的规划、逻辑改写规则的应用、物理算子的选择等。主流现代数据库的计划生成主要依赖基于统计信息的代价估算,而在统计信息频繁跳变、数据倾斜、多表链接等复杂场景下,用户常常需要使用 Plan Hint 对查询计划进行固化。
Plan Hint 是语句语法的一部分,当前 openGauss 提供的能力包括指定基表扫描路径和索引使用、Join 的顺序及其物理算子、指定结果集行数、指定语句级生效的 GUC 参数等。但在实际的生产业务中,直接修改语句使得 Plan Hint 生效的成本极大,需要对用户业务升级并发新版本,导致问题响应时间达到小时级。
openGauss 自 3.1.0 版本引入了 SQLPatch 功能,能够在避免直接修改业务语句,通过调用数据库提供的接口,对指定的查询语句模板进行 hint 调优,从而大幅提高查询计划性能问题的解决效率。
在创建 SQLPatch 时,用户需要首先获取目标语句的唯一编号(unique SQL ID),这样的好处时能够在很大程度上提高 SQL Patch 的命中率,因为唯一编号在生成时会对绑定的参数变量以及硬编码在语句条件中的常量参数进行归一化,从而忽视这些变量的不同。需要注意的是,语句唯一编号的生成过程需要使用到查询语句的语义信息,因此即使是相同的语句在不同的数据库之间也是不同的,而 SQLPatch 的生效范围也是数据库级别,需要用户在业务库中创建正确的 SQLPatch 才能够正确地影响计划。
SQLPatch 会在被创建后即刻生效,并且会处理其对应的计划缓存使之失效并根据 SQLPatch 的内容重新生成计划,而在备机读的场景下,主机创建 SQLPatch 的语句需要在日志同步到备机并回放后才能再备机上生效,因此可能存在一定的时间窗。
正确地使用 SQLPatch 可以将单次业务计划调优的任务量从业务升级降级为单条运维语句,大大提升了运维效率,但不建议业务长期依赖 SQLPatch,最好在业务例行升级时将调优语句固化到业务中,提升业务稳定性。
场景示例:使用 SQL-PATCH 对特定语句进行 Hint 调优
openGauss=# set track_stmt_stat_level = 'L1,L1'; --打开FullSQL统计信息
SET
openGauss=# select * from hint_t1 t1 where t1.a = 1; --执行SQL语句
a | b | c
---+---+---
1 | 1 | 1
(1 row)
openGauss=# select unique_query_id, query, query_plan from dbe_perf.statement_history where query like '%hint_t1%'; --获取查询计划和Unique SQL ID
-[ RECORD 1 ]---+----------------------------------------------------------------------------------------------
unique_query_id | 2578396627
query | select * from hint_t1 t1 where t1.a = ?;
query_plan | Datanode Name: sgnode
| Bitmap Heap Scan on hint_t1 t1 (cost=4.33..15.70 rows=10 p-time=0 p-rows=0 width=12)
| Recheck Cond: (a = '***')
| -> Bitmap Index Scan on hint_t1_a_idx (cost=0.00..4.33 rows=10 p-time=0 p-rows=0 width=0)
| Index Cond: (a = '***')
|
|
openGauss=# select * from dbe_sql_util.create_hint_sql_patch('patch1', 2578396627, 'indexscan(t1)'); -- 对指定的Unique SQL ID指定Hint Patch
-[ RECORD 1 ]---------+--
create_hint_sql_patch | t
openGauss=# explain select * from hint_t1 t1 where t1.a = 1; -- 通过explain可以确认Hint是否生效
NOTICE: Plan influenced by SQL hint patch
QUERY PLAN
-----------------------------------------------------------------------------------
[Bypass]
Index Scan using hint_t1_a_idx on hint_t1 t1 (cost=0.00..32.43 rows=10 width=12)
Index Cond: (a = 1)
(3 rows)
openGauss=# select * from hint_t1 t1 where t1.a = 1; -- 再次执行语句
a | b | c
---+---+---
1 | 1 | 1
(1 row)
openGauss=# select unique_query_id, query, query_plan from dbe_perf.statement_history where query like '%hint_t1%'; -- 可以看到新的执行记录计划已改变
-[ RECORD 1 ]---+--------------------------------------------------------------------------------------------------
unique_query_id | 2578396627
query | select * from hint_t1 t1 where t1.a = ?;
query_plan | Datanode Name: sgnode
| Bitmap Heap Scan on hint_t1 t1 (cost=4.33..15.70 rows=10 p-time=0 p-rows=0 width=12)
| Recheck Cond: (a = '***')
| -> Bitmap Index Scan on hint_t1_a_idx (cost=0.00..4.33 rows=10 p-time=0 p-rows=0 width=0)
| Index Cond: (a = '***')
|
|
-[ RECORD 2 ]---+--------------------------------------------------------------------------------------------------
unique_query_id | 2578396627
query | select * from hint_t1 t1 where t1.a = ?;
query_plan | Datanode Name: sgnode
| Index Scan using hint_t1_a_idx on hint_t1 t1 (cost=0.00..8.27 rows=1 p-time=0 p-rows=0 width=12)
| Index Cond: (a = '***')
|
|
复制代码
通过上面的案例,我们可以看到 SQLPatch 功能可以很容易地改变 SQL 语句的执行计划,并且代价和成本极低,这为 openGauss 的使用带来了一种更加便捷的方式。未来,我们还会在此基础上演进出更富有竞争力的产品特性,欢迎大家关注。
评论