为什么复杂 SQL 会'爆内存'?
在金融、政务等核心系统中,业务逻辑往往极其复杂。为了代码的可读性和维护性,开发人员倾向于使用多层嵌套子查询、公用表表达式(CTE)以及窗口函数来组织 SQL。例如,一个典型的风控查询可能长这样:
SELECT * FROM (
SELECT DISTINCT customer_id, account_no, balance
FROM transaction_history
WHERE transaction_date >= '2024-01-01'
) AS recent_customers
JOIN customer_profile ON recent_customers.customer_id = customer_profile.cust_id
WHERE customer_profile.cust_type = 'VIP' AND customer_profile.region = '华东';
这个查询的意图很明确:先从交易历史表中获取今年以来的所有客户去重记录,再与客户信息表连接,筛选出华东地区的 VIP 客户。然而,这种写法却埋下了巨大的性能隐患。
传统数据库执行流程的局限性
在没有智能优化的情况下,大多数传统数据库优化器会采用一种机械的执行顺序:
- 无脑全扫子查询:首先,优化器会完全执行子查询。无论外层条件最终会过滤掉多少数据,数据库都会对
transaction_history表进行全表扫描,并对结果进行去重操作,生成一个庞大的中间结果集。如果表有 1 亿行,即使符合条件的只有几千万行,中间结果也可能非常巨大。 - 后续才进行过滤和连接:将这个庞大的中间结果与
customer_profile表进行 JOIN 操作,此时才应用cust_type = 'VIP'和region = '华东'这两个过滤条件。如果 VIP 客户占比很小,意味着中间结果中 99% 的数据在 JOIN 过程中都会被丢弃。 - 性能瓶颈的产生:问题就在于,筛选表上的高效过滤条件无法提前作用于子查询的扫描阶段。
transaction_history表扫描了大量最终根本不会被 JOIN 命中的数据,消耗了巨量的 CPU、内存和 I/O 资源。在内存有限的情况下,甚至可能触发磁盘交换,导致性能急剧下降。
更复杂场景下的灾难性后果
在实际生产环境中,SQL 往往更加复杂,可能包含多层嵌套、UNION、窗口函数等。例如:
WITH ranked_trans AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY cust_id trans_date ) rn
transaction_log
)
ranked_trans rt
customer c rt.cust_id c.cust_id
order_summary os c.cust_id os.cust_id
c.cust_level os.total_amount ;


