暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

【金仓数据库征文】_无须调整应用代码:Query mapping 让烂SQL起死回生的魔法

原创 听见风的声音 2025-06-24
279

1 query mapping–破解SQL优化需要改应用代码的困境

1.1 一个ORACLE数据库的SQL优化的场景

某国际物流公司的全球运单追踪系统面临严峻性能挑战:当客户在网站或APP输入运单号查询包裹状态时,对应这个查询的SQL语句如下

SELECT /*+ GATHER_PLAN_STATISTICS */ s.tracking_no, c.name, r.route_path, d.amount, s.last_update_time FROM shipments s JOIN customers c ON s.cust_id = c.id JOIN route_history r ON s.id = r.ship_id JOIN duty_info d ON s.id = d.ship_id WHERE s.tracking_no = 'UPS7829HKG' -- 输入运单号 AND r.is_latest = 'Y' -- 仅取最新路由

系统需要实时关联订单主表、客户信息、最新路由记录及关税明细等8张大表(日均增长50万条,总量超20亿)。在业务高峰期,这项关键查询的响应时间飙升到12秒以上,引发大量的客户投诉。
技术团队深入排查后发现症结所在:Oracle优化器严重误判了路由历史表的返回行数(实际过滤后仅1条记录,却被估算为1万行),导致错误选择了低效的嵌套循环连接方式;同时由于SQL语句中隐含的OR条件冲突(s.tracking_no OR c.phone),本该启用的运单号索引完全失效。更棘手的是,该查询来自封闭的第三方货运平台,企业无权修改原始SQL代码。
面对这种"看得见病灶却无法动手术"的困境,DBA团队启用了Oracle的SQL Profile技术实施精准干预:

-- 创建调优任务锁定问题SQL EXEC DBMS_SQLTUNE.CREATE_TUNING_TASK( sql_id => 'g7s2k9hna1v7p', task_name => 'FIX_SHIPMENT_QUERY' ); -- 接受优化建议生成Profile EXEC DBMS_SQLTUNE.ACCEPT_SQL_PROFILE( task_name => 'FIX_SHIPMENT_QUERY', name => 'SHIPMENT_TRACKING_PROFILE', force_match => TRUE -- 强制匹配所有相似SQL结构 );

这个SQL profile注入两个关键修正:首先将致命的嵌套循环连接替换为高效的哈希连接,其次绕过索引冲突点,强制启用shipments.tracking_idx核心索引。优化效果堪称奇迹——逻辑I/O从38,921次暴降至127次,执行时间从11.8秒锐减到0.2秒,数据库CPU负载直接"腰斩"。

1.2 金仓数据库的query mapping

类似前面的场景在Oracle数据库中经常可以看到,sql profile也是Oracle DBA必须掌握的SQL 优化技巧之一(要是真不会这个,不能说是合格的Oracle 工程师吧)。在目前信创的大潮下,国产数据库在关键业务上替换Oracle的工作正在有条不紊的开展,也取得了明显可见的成效。在SQL优化方面,也有国产数据库提供了类似Oracle sql profile的技术,比如openGauss的AI 执行计划固化,阿里PolarDB的执行计划 Outline,今天介绍一下金仓数据库的query mapping
中电科金仓(北京)科技股份有限公司(以下简称“电科金仓”)成立于1999年,是成立最早的拥有自主知识产权的国产数据库企业,也是中国电子科技集团(CETC)成员企业。电科金仓以“提供卓越的数据库产品助力企业级应用高质量发展”为使命,致力于“成为世界卓越的数据库产品与服务提供商”。
KingbaseES(简称KES)是电科金仓面向全行业、全客户关键应用的提供的企业级大型通用融合数据库产品,适用于事务处理、数据分析、海量时序数据采集检索、互联网等应用场景;可用作管理信息系统、业务及生产系统、决策支持系统、多维数据分析系统、运行日志管理系统、全文检索系统、地理信息系统、时序数据处理相关系统的承载数据库。 KES采用融合数据库架构,通过多语法体系一体化架构兼容Oracle、MySQL、SQL Server、PostgreSQL等主流数据库的语法; 采用多模数据一体化存储,支持对关系模型、文档模型、全文本、GIS数据、时序等数据的统一存储、混合访问、模型间转换; 采用集中分布一体化架构,提供不同级别的可用性。

Query Mapping 是KingbaseES提供的的功能,允许用户预先创建 SQL 语句的映射关系并储存在相应的系统表,当用户输入的 SQL 语句与所创建的映射关系相匹配时,替换成映射的 SQL 语句去实际执行。这个功能可用于数据库迁移和优化的场景。

1.3 query mapping的原理

Query Mapping 主要作用是做 SQL 的匹配/替换工作,在数据库中配置源 SQL 语句到目标 SQL 语句的映射关系(存在系统表),根据客户的输入 sql 语句去做匹配,如果匹配,就替换成目标语句进行执行,它有两种级别:
• TEXT 级别:不做词法/语法/语义检查,保存用户的原始 sql 和期望 mapping 的目标 sql,进行字符串匹配
image.png
• SEMANTICS 级别:做词法/语法/语义语义检查,并保存用户原始/目标 sql 对应的 Query tree
image.png

2 检查query mapping是否激活

kingbase=# select name,setting,boot_val,reset_val from pg_settings where name='enable_query_rule'; name | setting | boot_val | reset_val -------------------+---------+----------+----------- enable_query_rule | off | off | off --编辑kingbase.conf,将enable_query_rule的值改为on [root@localhost data]# grep enable_query_rule kingbase.conf enable_query_rule = on # query mapping --重启数据库,再次检查enable_query_rule的值,当前设置为on kingbase=# select name,setting,boot_val,reset_val from pg_settings where name='enable_query_rule'; name | setting | boot_val | reset_val -------------------+---------+----------+----------- enable_query_rule | on | off | on (1 row)

3 第一条query mapping

使用create_query_rule函数创建query mapping规则,创建一条简单的映射规则的语句如下

kingbase=# SELECT create_query_rule(
kingbase(# 'qm1', 
kingbase(# 'select $1::TEXT AS col', 
kingbase(# 'select 2222', 
kingbase(# true, 
kingbase(# 'semantics'
kingbase(# );
 create_query_rule
-------------------
(1 row)

上面的语句创建了一条名为qm1的的语义级别的query mapping规则,把语句 select $1::TEXT AS col 映射为 select 2222,$1为文本型的变量,执行时可以替换为任何类型为text的值,这这条虽则立即生效。

3.2 查看创建的query mapping

规则创建后,保存在系统表_qrymapping内,这个表的定义如下

kingbase=# \d _qrymapping Table "pg_catalog._qrymapping" Column | Type | Collation | Nullable | Default ----------------------+----------+-----------+----------+--------- oid | oid | | not null | qmname | name | | not null | qmenable | boolean | | not null | qmfromsql | text | c | | qmreplacedsql | text | c | | qmcompiledfromsql | text | c | | qmcompiledreplacesql | text | c | | qmlevel | smallint | | | Indexes: "_qrymapping_name_index" UNIQUE, btree (qmname) "_qrymapping_oid_index" UNIQUE, btree (oid)

这个表可以查询到映射前后语句的原始文本,也保存了语句的编译后的文本,在qmname和oid创建了唯一索引,这应当是为了支持大量规则时的系统和用户查询。查询这个视图可以看到刚才创建的那条规则

kingbase=# select qmname,qmfromsql,qmreplacedsql,qmenable from _qrymapping; qmname | qmfromsql | qmreplacedsql | qmenable --------+------------------------+---------------+---------- qm1 | select $1::TEXT AS col | select 2222 | t (1 row)
3.3 验证规则
--- 执行query mapping的原始sql语句,发现语句被按照规则替换了 kingbase=# select 'aaa' as col; ?column? ---------- 2222 (1 row) --删除规则后再看 kingbase=# SELECT drop_query_rule('qm1') kingbase=# select 'aaa' as col; ?column? ---------- aaa (1 row)

4 query mapping的操作

金仓数据库支持对创建的query mapping规则进行激活,关闭,删除三种操作,每一操作可以针对一条规则或者全部规则,对应函数如下
image.png

5 query mapping的几个应用场景

5.1 连接数据库,创建测试表,载入数据
kingbase=# ]\c test You are now connected to database "test" as userName "kingbase". --创建测试表 test=# create table t1(id int primary key, val int, name varchar(64)); CREATE TABLE test=# create table t2(id int, val int, name varchar(64)); CREATE TABLE --表中加入数据 test=# insert into t1 select i, i%10, 'Kingbase'||(i%5) from generate_series(1,1000) as x(i); INSERT 0 1000 test=# insert into t2 select i, i%20, 'Kingbase'||(i%10) from generate_series(1,1000) as x(i); INSERT 0 1000
5.2 变量替换
5.2.1 示例语句
test=# select id,val from t1 where id<10; id | val ----+----- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 8 | 8 9 | 9 (9 rows)
5.2.2 替换规则

替换语句选择条件,这里将原来的条件$1替换为$1-5,可以执行多种替换,也可以增加,减少,改变语句的选择条件。

test=# SELECT create_query_rule('qm1', 'select id,val from t1 where id<$1','select id from t1 where id<( test'# $1-5)', true, 'text');--将$1 替换为$1-5 create_query_rule ------------------- (1 row)
5.2.3 替换效果
test=# select id,val from t1 where id<10; id ---- 1 2 3 4 (4 rows)
5.2 替换操作目标

query mapping的另一个用途是替换操作目标,可以用于数据清理时的场景。有时,数据库里的日志表积累了大量数据,一年之前的数据基本没什么访问需求,而这些日志是随时写入的,直接truncte会影响会影响对日志记录的写入和对当前数据库的访问。这时,query mapping可以派上用场,可以先创建一个空表,把近期的数据导入到空表中,然后用query mapping将对原表的访问改到对新建表的访问。为了减少切换的时间,可以在创建规则时设置规则为不激活,在创建完所有规则后一次性激活。比如,下面的规则,将对表t1的插入操作改到对表T2上

select create_query_rule('qm10', 'insert into t1 values (1001,0)','insert into t2 values (1001,0)',true,'text'); test=# insert into t1 values (1001,0); INSERT 0 1 test=# select * from t1 where id=1001; (0 row) test=# select * from t2 where id=1001; id | val | name ------+-----+------ 1001 | 0 | (1 row)
5.3 调整SQL语句的hint

hint是SQL语句常用的技巧,大概也是开发最了解的SQL优化技巧之一,在日常的优化中,有使用了hint提高了SQL语句性能的,更常见的是使用了不恰当的hint反而导致优化器不能选择正常的执行计划,反而导致SQL执行性能严重恶化的,下面是一个使用了不恰当的并行hint导致SQL性能恶化的例子

test=# \timing on Timing is on. test=# select /*+Parallel(t1 6) */ count(*) from t1; count ------- 1001 (1 row) Time: 16.411 ms

语句使用了并行hint,执行计划如下

test=# explain analyze select /*+Parallel(t1 6) */ count(*) from t1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- Aggregate (cost=2.50..2.51 rows=1 width=8) (actual time=36.288..38.369 rows=1 loops=1) -> Gather (cost=0.00..0.00 rows=1000 width=0) (actual time=1.410..38.314 rows=1001 loops=1) Workers Planned: 6 Workers Launched: 4 -> Parallel Seq Scan on t1 (cost=0.00..0.00 rows=417 width=0) (actual time=0.004..0.021 rows=200 loops=5) Planning Time: 0.363 ms Execution Time: 38.898 ms (7 rows)

语句的并行hint生效了,hint要求的Workers是6个,实际执行了4个(这个数据库的容器只分配了4个CPU),使用query mapping关闭这条语句的并行hint

test=# SELECT create_query_rule( test(# 'qmtp', test(# 'select /*+Parallel(t1 6) */ count(*) from t1', test(# 'select count(*) from t1', test(# true, test(# 'text' test(# ); create_query_rule ------------------- (1 row)

执行一下语句看看

test=# select /*+Parallel(t1 6) */ count(*) from t1; count ------- 1001 (1 row) Time: 0.700 ms

语句的执行时间从16.411 ms降到了0.700 ms,看一下语句的执行计划

test=# explain (usingquerymapping) select /*+Parallel(t1 6) */ count(*) from t1; QUERY PLAN ------------------------------------------------------------ Aggregate (cost=19.50..19.51 rows=1 width=8) -> Seq Scan on t1 (cost=0.00..17.00 rows=1000 width=0) (2 rows)

需要使用usingquerymapping才能显示query mapping生效后的执行计划,query mapping生效后,语句不再执行并行扫描。

6 注意事项

执行query mapping的SQL语句,与未执行之前相比,需要额外执行读取系统表以执行语句的替换,必然对性能造成一定的影响。对于本身执行性能已经恶化的SQL语句,这种性能损耗可以忽略不计。如果是在高并发的执行时间非常短的SQL语句(如 insert,带唯一条件的update),这种性能损耗可能会造成显著的影响。因此,在实施query mapping时,需要审慎的评估场景及其对性能的影响。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论