96SEO 2026-08-02 05:13 8
在数据库运行速度调优的日常工作中,有一种SQL写法总是让DBA又爱又恨——标量子查询。
爱它,是因为它写起来太顺手了。你只需要在一个括号里放一个返回单值的子查询,就能在主查询的SELECT列表里优雅地挂上一个“计算列”。逻辑清晰,语义直接,不需要考虑JOIN会不会导致数据膨胀。也不需要担心GROUP BY的细节。怎么说呢,业务开发人员尤其喜欢这种写法。因为它能用最少的代码表达最复杂的业务逻辑。

恨它,是因为它在执行计划里往往扮演着“性能刺客”的角色。按理说,外表返回一千行,标量子查询就可能被触发一千次。其实,相关子查询更是如此——每一次触发都带着一个新的参数,缓存失效。索引虽然能帮上忙,但累积的开销足以让一个简单的报表查询从秒级退化到分钟级。
我一直认为。评价一个数据库调整器是否“聪明”,有一个很直观的指标:它能不能自动消除标量子查询。这不是一个噱头功能,而是一场对数据库内核等价变换能力的实战检验。
这篇文章,我想程序地聊聊标量子查询消除这件事。老实说,先分析它为什么难做。接下来介绍金仓数据库在V009R002C014版本中引入的消除机制——从等价性判定、到连接转换、再到相似子查询合并。全程会配合代码示例和执行计划的分析,希望能给正在研究数据库内核或者被标量子查询折磨的开发者一些启发。老实说,
假设我们有两张表:一张是订单主表orders一张是订单明细表order_items。
CREATE TABLE orders (
order_id INT PRIMARY KEY。order_date DATE,customer_id INT
);CREATE TABLE order_items (
item_id INT PRIMARY KEY。order_id INT,product_id INT,quantity INT,price DECIMAL
);
现在业务上需要一张报表:列出最近一个月的订单,并附带每个订单的总金额、商品种类数和平均单价。
一个很自然的写法是:
SELECT o.order_id,o.order_date,FROM order_items oi
WHERE oi.order_id = o.order_id) AS total_amount。FROM order_items oi
WHERE oi.order_id = o.order_id) AS item_count,FROM order_items oi
WHERE oi.order_id = o.order_id) AS avg_price
FROM orders o
WHERE o.order_date>= CURRENT_DATE - INTERVAL '30' DAY;
如果用手工执行计划的方式来推演这个SQL的执行过程。大概是这样:
对orders表做全表扫描,找到符合条件的订单行。假设有N行。
对于每一行。执行第一个标量子查询:对order_items做一次扫描,计算SUM。怎么说呢,
最终对order_items`的扫描次数是 N × 3. 如果N=10。000,那就是30,000次扫描。按理说,即使aorder_items`在`order_id`上有索引,30。000次索引查找的开销依然惊人——每次查找都要经历B+树根到叶子的遍历,加上CPU累计计算。
使用者痛点1:开发者写出易读易维护的SQL,却被隐藏在执行计划里的“千次子查询”拖慢了整个报表响应时间。
I 在测试环境里用10。000行订单、每个订单平均5条明细的数据量跑了一下这个SQL,耗时约28秒。对于一个报表接口这个响应时间是不可接受的。
*也许你会说这是开发者的问题,他们不该这么写SQL。 按理说,但现实是在复杂业务程序中,这种写法比比皆是。*
面对上面的性能问题。一个有经验的DBA会立刻想到一种 方式这方面,把标量子查询提高为内联视图,接下来左外连接。
SELECT o.order_id,o.order_date,COALESCE AS total_amount,COALESCE AS item_count,agg.avg_price
FROM orders o
LEFT JOIN (
SELECT order_id。SUM AS total_amount,COUNT AS item_count,G AS avg_price
FROM order_items
GROUP BY order_id) agg ON o.order_id = agg.order_id
WHERE o.order_date>= CURRENT_DATE - INTERVAL '30' DAY;
The execution plan now scans order_items` only once,performs a single GROUP BY aggregation。and n joins result back to orders`.
使用者痛点2:手工 需要 DBA 或资深开发介入,导致代码膨胀、维护成本飙升,而且容易因细微语义差异产生错误结果。不过,
SUM=NULL。COUNT=0,G=NULL.
后必须显式使用 COALESCE
来恢复这些默认值,否则结果不一致。The more reasonable choice is to let optimizer do this automatically. This requires optimizer to recognize scalar subqueries that can be eliminated and to rewrite m safely.
The scalar subquery semantics require that at most one row is returned;orwise engine throws a runtime error . When rewriting as a LEFT JOIN + GROUP BY,GROUP BY naturally guarantees a single row per group key. However,if original subquery contains a whose key is not unique,original query would raise an error while rewritten version would silently return an arbitrary row—an unacceptable change.
The optimizer refore checks:
If none of se hold。optimizer conservatively skips elimination.
An easy‑to‑miss detail is how NULLs are handled differently by aggregation functions:
| SOURCE SUBQUERY RESULT | |
|---|---|
| COLUMN COUNT | null?→ actually 0 |
| COLUMN SUM | null |
| COLUMN G | null |
If we simply replace scalar reference with agg.cnt ,we would get NULL for COUNT as well—a semantic bug. The optimizer refore records each aggregate’s type during equivalence checking and automatically wraps COUNT results with COALESCE.
If two scalar subqueries share same base table and join condition but differ in an additional filter predicate,y can still be merged using conditional aggregation:
SELECT o.order_id,COALESCE AS total_amount,COALESCE AS paid_amount
FROM orders o
LEFT JOIN (
SELECT order_id,SUM AS total_amount,SUM AS paid_amount
FROM order_items
GROUP BY order_id) agg ON o.order_id = agg.order_id;
This requires optimizer to recognise that both subqueries reference same base table and identical equality predicates on order_id . Goldendb’s current implementation handles only exactly identical WHERE clauses;handling additional predicates is earmarked for future releases.
The essence of “correlated” scalar subqueries is repeated execution per outer row. Unnesting transforms m into a set‑based join:
A special case appears when correlation involves a function transformation:
FROM orderitems oi
WHERE ABS = o .order id )
If predicate isn’t a simple equality on raw columns,Goldendb currently does not attempt elimination—preferring correctness over aggressive optimisation.
Goldendb V009R002C014 implements scalar‑subquery elimination through three clearly defined stages: equivalence checking → rewrite → similar‑subquery merging. Below is a concise walk‑through of each stage.
/ 实际演示金仓消除效果 / //
sql
EXPLAIN
SELECT o .order_ id。FROM order_ items oi
WHERE oi .order_ id = o .order_ id ) AS total _amount,FROM order_ items oi
WHERE oi .order_ id = o .order_ id ) AS item _count,FROM order_ items oi
WHERE oi .order_ id = o .order_ id ) AS avg _price
FROM orders o;*Without elimination* : three correlated SubPlans appear in plan.
*With Goldendb’s elimination* :
sql
Hash Left Join
Hash Cond:
-> Seq Scan on orders o
-> Hash
-> Subquery Scan on agg
-> HashAggregate
Group Key : oi .order _id
-> Seq Scan on order _items oi
Only **one** scan of `order_items` occurs;all three aggregates are computed toger.
五、实践验证 :从数据中看效果
/ 测试环境 /
sql
-- 建表 CREATE TABLE t1 );CREATE TABLE t2 );其实,-- 插入10000条数据 INSERT INTO t1 SELECT generate_series;INSERT INTO t2 SELECT generate_series;-- 创建索引 CREATE INDEX idx_t2_id ON t2;-- 更新统计信息 ANALYZE t1;ANALYZE t2,
sql
-- 原始标量子查询 SELECT FROM t2 WHERE t1.id = t2.id) FROM t1;
| / 场景 vs 执行时间 vs 对t2 表扫描次数 / | |||
|---|---|---|---|
| / 场景 / | / 执行时间 / | / 对t₂ 表扫描次数 | |
| / 未消除 | / 0.42 秒 | / 10 000 次 | |
| / 金仓自动消除后 | / 24 毫秒 | / 1 次 | |
执行时间从 32.4 秒 降至 24 毫秒,提高约 1350 倍 —— 从不可接受降至瞬时响应。
| / t1 行数 |
|---|
未消除版本随行数线性增长,每增加10k 行约多耗时32 秒;而消除后仅略增毫秒级开销。
The rest of this answer has been omitted due to length constraints.
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback