不止热门角色,我们为你扩展了更多细分角色分类,覆盖职场提升、商业增长、内容创作、学习规划等多元场景。精准匹配不同目标,让每一次生成都更有方向、更高命中率。
立即探索更多角色分类,找到属于你的增长加速器。
非SARGable谓词
N+1 型相关子查询
重复扫描与不必要的计算
临时表管理与索引
统计口径与可优化点
谓词与语义
去除相关子查询,改为分组一次性聚合+JOIN
临时表与索引
基表索引建议(一次性执行)
其他
以下为等价语义的重构版(MySQL 8+),去除了相关子查询,修复谓词,按阶段性聚合,显式索引临时表。
原始关键片段(问题点示例):
优化后完整过程: DELIMITER // CREATE PROCEDURE sp_order_summary( IN p_shop_id BIGINT, IN p_start_date DATETIME, IN p_end_date DATETIME, IN p_limit INT ) BEGIN DECLARE v_start DATE; DECLARE v_end DATE; DECLARE v_limit INT;
SET v_limit = IFNULL(p_limit, 500);
SET v_start = DATE(IFNULL(p_start_date, NOW() - INTERVAL 30 DAY));
SET v_end = DATE(IFNULL(p_end_date, NOW()));
IF v_end < v_start THEN
SET v_end = v_start;
END IF;
-- 1) 候选订单集(时间窗 + 可选店铺)
DROP TEMPORARY TABLE IF EXISTS tmp_orders;
IF p_shop_id IS NULL THEN
CREATE TEMPORARY TABLE tmp_orders AS
SELECT o.id AS order_id, o.user_id, o.created_at, o.status
FROM orders o
WHERE o.created_at >= v_start
AND o.created_at < v_end + INTERVAL 1 DAY;
ELSE
CREATE TEMPORARY TABLE tmp_orders AS
SELECT o.id AS order_id, o.user_id, o.created_at, o.status
FROM orders o
WHERE o.shop_id = p_shop_id
AND o.created_at >= v_start
AND o.created_at < v_end + INTERVAL 1 DAY;
END IF;
ALTER TABLE tmp_orders
ADD PRIMARY KEY (order_id),
ADD KEY idx_tmp_orders_user (user_id),
ADD KEY idx_tmp_orders_created (created_at);
-- 2) 订单商品聚合(一次性按订单聚合)
DROP TEMPORARY TABLE IF EXISTS tmp_oi_agg;
CREATE TEMPORARY TABLE tmp_oi_agg AS
SELECT
oi.order_id,
COUNT(*) AS item_count,
SUM(oi.quantity) AS total_qty,
SUM(oi.quantity * oi.price) AS total_amount
FROM order_items oi
JOIN tmp_orders t ON t.order_id = oi.order_id
GROUP BY oi.order_id;
ALTER TABLE tmp_oi_agg ADD PRIMARY KEY (order_id);
-- 3) 订单状态日志聚合(一次性取每单最新状态时间)
DROP TEMPORARY TABLE IF EXISTS tmp_status_agg;
CREATE TEMPORARY TABLE tmp_status_agg AS
SELECT
l.order_id,
MAX(l.created_at) AS last_status_change
FROM order_status_log l
JOIN tmp_orders t ON t.order_id = l.order_id
GROUP BY l.order_id;
ALTER TABLE tmp_status_agg ADD PRIMARY KEY (order_id);
-- 4) 用户在时间窗内的订单数与GMV(可选,GMV如不需要可省略以进一步降负)
DROP TEMPORARY TABLE IF EXISTS tmp_user_metric;
CREATE TEMPORARY TABLE tmp_user_metric AS
SELECT
t.user_id,
COUNT(*) AS order_cnt,
SUM(COALESCE(i.total_amount,0)) AS gm_value
FROM tmp_orders t
LEFT JOIN tmp_oi_agg i ON i.order_id = t.order_id
GROUP BY t.user_id;
ALTER TABLE tmp_user_metric ADD PRIMARY KEY (user_id);
-- 5) 用户全量取消单数(仅针对候选用户集合统计)
DROP TEMPORARY TABLE IF EXISTS tmp_cancel_cnt;
CREATE TEMPORARY TABLE tmp_cancel_cnt AS
SELECT u.user_id, COUNT(*) AS cancel_cnt
FROM orders o
JOIN tmp_user_metric u ON u.user_id = o.user_id
WHERE o.status = 'CANCELLED'
GROUP BY u.user_id;
ALTER TABLE tmp_cancel_cnt ADD PRIMARY KEY (user_id);
-- 6) 最终结果
SELECT
t.order_id,
t.user_id,
t.created_at,
t.status,
COALESCE(i.item_count, 0) AS item_count,
COALESCE(i.total_qty, 0) AS total_qty,
COALESCE(i.total_amount, 0) AS order_amount,
COALESCE(u.order_cnt, 0) AS order_cnt,
COALESCE(c.cancel_cnt, 0) AS cancel_cnt,
s.last_status_change
FROM tmp_orders t
LEFT JOIN tmp_oi_agg i ON i.order_id = t.order_id
LEFT JOIN tmp_user_metric u ON u.user_id = t.user_id
LEFT JOIN tmp_cancel_cnt c ON c.user_id = t.user_id
LEFT JOIN tmp_status_agg s ON s.order_id = t.order_id
ORDER BY t.created_at DESC
LIMIT v_limit;
-- 7) 清理
DROP TEMPORARY TABLE IF EXISTS tmp_cancel_cnt;
DROP TEMPORARY TABLE IF EXISTS tmp_user_metric;
DROP TEMPORARY TABLE IF EXISTS tmp_status_agg;
DROP TEMPORARY TABLE IF EXISTS tmp_oi_agg;
DROP TEMPORARY TABLE IF EXISTS tmp_orders;
END // DELIMITER ;
一次性索引(在业务低峰期执行):
去除相关子查询:
谓词可索引化:
临时表索引化:
资源节省:
注:具体收益需基于你的数据分布、并发与硬件进行 EXPLAIN/EXPLAIN ANALYZE 与基准验证。
正确性与回归
会话与临时表
索引上线
参数与边界
监控与回滚
以上方案遵循 MySQL 最佳实践:去函数化谓词、避免 OR 导致的索引失效、用一次性聚合替代相关子查询、为临时表添加索引与减少重复扫描,通常可获得显著且稳定的性能提升。
游标与标量函数逐行调用
非SARGable时间过滤
NOLOCK 的数据一致性风险
临时表与ORDER BY
并发与重复结算风险
索引使用不明确
结构性优化(代码简化与集合化处理)
UDF调用优化
并发与一致性
索引优化(示例DDL)
参数敏感性
下方提供两种等价的集合化实现。优先推荐“单语句更新+输出”版本(更简洁、原子性好)。
CREATE OR ALTER PROCEDURE dbo.SettleTransactions
@BatchDate DATE,
@MerchantId BIGINT = NULL
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @feeRate DECIMAL(5,4) = 0.005;
DECLARE @EndOfDay DATETIME = DATEADD(DAY, 1, @BatchDate);
BEGIN TRAN;
-- 集合化:更新未结算交易并将结果写入台账
UPDATE t
SET t.settled = 1,
t.settle_batch_date = @BatchDate
OUTPUT
inserted.id AS tx_id,
inserted.merchant_id,
inserted.amount AS gross_amount,
f.fee AS fee,
inserted.amount - f.fee AS net_amount,
@BatchDate AS settle_date
INTO dbo.settlement_ledger (tx_id, merchant_id, gross_amount, fee, net_amount, settle_date)
FROM dbo.payment_tx AS t WITH (UPDLOCK, ROWLOCK)
CROSS APPLY (SELECT dbo.fn_calc_fee(t.amount, t.pay_channel, @feeRate) AS fee) AS f
WHERE t.settled = 0
AND t.tx_time < @EndOfDay
AND (@MerchantId IS NULL OR t.merchant_id = @MerchantId)
AND EXISTS (SELECT 1 FROM dbo.payment_tx_detail AS d WHERE d.tx_id = t.id);
-- 可按需评估 OPTION (RECOMPILE)
COMMIT TRAN;
-- 汇总报表
SELECT
m.merchant_name,
COUNT(*) AS tx_cnt,
SUM(l.gross_amount) AS total_amt,
SUM(l.fee) AS total_fee
FROM dbo.settlement_ledger AS l
JOIN dbo.merchant AS m ON m.id = l.merchant_id
WHERE l.settle_date = @BatchDate
GROUP BY m.merchant_name
ORDER BY total_amt DESC;
END
CREATE OR ALTER PROCEDURE dbo.SettleTransactions
@BatchDate DATE,
@MerchantId BIGINT = NULL
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @feeRate DECIMAL(5,4) = 0.005;
DECLARE @EndOfDay DATETIME = DATEADD(DAY, 1, @BatchDate);
DECLARE @SettledIds TABLE (id BIGINT PRIMARY KEY);
BEGIN TRAN;
INSERT INTO dbo.settlement_ledger (tx_id, merchant_id, gross_amount, fee, net_amount, settle_date)
OUTPUT inserted.tx_id INTO @SettledIds(id)
SELECT
t.id,
t.merchant_id,
t.amount,
f.fee,
t.amount - f.fee,
@BatchDate
FROM dbo.payment_tx AS t
CROSS APPLY (SELECT dbo.fn_calc_fee(t.amount, t.pay_channel, @feeRate) AS fee) AS f
WHERE t.settled = 0
AND t.tx_time < @EndOfDay
AND (@MerchantId IS NULL OR t.merchant_id = @MerchantId)
AND EXISTS (SELECT 1 FROM dbo.payment_tx_detail AS d WHERE d.tx_id = t.id);
UPDATE t
SET t.settled = 1,
t.settle_batch_date = @BatchDate
FROM dbo.payment_tx AS t
JOIN @SettledIds AS s ON s.id = t.id;
COMMIT TRAN;
SELECT
m.merchant_name,
COUNT(*) AS tx_cnt,
SUM(l.gross_amount) AS total_amt,
SUM(l.fee) AS total_fee
FROM dbo.settlement_ledger AS l
JOIN dbo.merchant AS m ON m.id = l.merchant_id
WHERE l.settle_date = @BatchDate
GROUP BY m.merchant_name
ORDER BY total_amt DESC;
END
说明:
A. 将日期函数改写为可走索引的范围谓词
B. 基表索引设计(优先级从高到低)
C. 临时表索引与统计信息
D. 连接与半连接写法
E. 报表目标表(可选)
仅展示与索引生效相关的重写(范围谓词、JOIN、临时表索引与ANALYZE);业务逻辑保持不变。
-- 推荐的永久索引(一次性部署,使用 CONCURRENTLY 降低锁影响) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_created ON users (created_at); -- 或:如果 segment 过滤选择性更强,改为: -- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_segment_created ON users (segment, created_at);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sessions_start_time_user ON sessions (start_time, user_id) INCLUDE (duration);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_created_user ON orders (created_at, user_id) INCLUDE (status, amount);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_events_user_occ_desc ON events (user_id, occurred_at DESC);
-- 可选(报表成品表避免重复) -- CREATE UNIQUE INDEX IF NOT EXISTS ux_daily_user_report ON daily_user_report(date_key, user_id);
-- 存储过程(仅展示核心SQL的“前后对比式”重构) CREATE OR REPLACE PROCEDURE sp_daily_user_report(p_date DATE, p_segment TEXT DEFAULT 'all') LANGUAGE plpgsql AS $$ BEGIN -- 1) tmp_users:改写时间谓词为范围,避免对列使用函数;必要时建索引与ANALYZE CREATE TEMP TABLE tmp_users AS SELECT u.id AS user_id, u.created_at, u.segment, u.region FROM users u WHERE u.created_at < p_date + interval '1 day' AND (p_segment = 'all' OR u.segment = p_segment);
CREATE INDEX ON tmp_users(user_id);
ANALYZE tmp_users;
-- 2) tmp_sessions:改为范围谓词 + 显式JOIN
CREATE TEMP TABLE tmp_sessions AS
SELECT s.user_id,
count(*) AS session_cnt,
sum(s.duration) AS total_duration
FROM sessions s
JOIN tmp_users tu ON tu.user_id = s.user_id
WHERE s.start_time >= p_date
AND s.start_time < p_date + interval '1 day'
GROUP BY s.user_id;
CREATE UNIQUE INDEX ON tmp_sessions(user_id);
ANALYZE tmp_sessions;
-- 3) tmp_orders:范围谓词 + JOIN
CREATE TEMP TABLE tmp_orders AS
SELECT o.user_id,
count(*) FILTER (WHERE o.status='PAID') AS paid_cnt,
sum(o.amount) FILTER (WHERE o.status='PAID') AS gm_value,
sum(o.amount) AS order_amt
FROM orders o
JOIN tmp_users tu ON tu.user_id = o.user_id
WHERE o.created_at >= p_date
AND o.created_at < p_date + interval '1 day'
GROUP BY o.user_id;
CREATE UNIQUE INDEX ON tmp_orders(user_id);
ANALYZE tmp_orders;
-- 4) tmp_last_event:范围谓词 + JOIN
-- 若业务允许放弃随机并列打散,可去掉 random(),显著提升利用 idx_events_user_occ_desc 的能力
CREATE TEMP TABLE tmp_last_event AS
SELECT DISTINCT ON (e.user_id)
e.user_id, e.event_name, e.occurred_at
FROM events e
JOIN tmp_users tu ON tu.user_id = e.user_id
WHERE e.occurred_at >= p_date
AND e.occurred_at < p_date + interval '1 day'
ORDER BY e.user_id, e.occurred_at DESC, random(); -- 如可移除 random(),更佳
CREATE UNIQUE INDEX ON tmp_last_event(user_id);
ANALYZE tmp_last_event;
-- 5) 最终装载
INSERT INTO daily_user_report(date_key, user_id, segment, session_cnt, total_duration, paid_orders, gm_value, last_event, last_event_time)
SELECT
p_date,
u.user_id,
u.segment,
COALESCE(s.session_cnt,0),
COALESCE(s.total_duration,0),
COALESCE(o.paid_cnt,0),
COALESCE(o.gm_value,0),
le.event_name,
le.occurred_at
FROM tmp_users u
LEFT JOIN tmp_sessions s ON s.user_id = u.user_id
LEFT JOIN tmp_orders o ON o.user_id = u.user_id
LEFT JOIN tmp_last_event le ON le.user_id = u.user_id;
RAISE NOTICE 'Report loaded for %, segment=%', p_date, p_segment;
DROP TABLE IF EXISTS tmp_last_event;
DROP TABLE IF EXISTS tmp_orders;
DROP TABLE IF EXISTS tmp_sessions;
DROP TABLE IF EXISTS tmp_users;
END; $$;
综合估计:在数据量较大的在线业务中,上述改造通常可带来数量级的查询时间缩短(例如从秒级/十秒级降至百毫秒-秒级,视数据量与硬件而定),并显著降低缓冲命中压力与I/O。
让 AI 扮演资深数据库优化顾问,面向电商订单、财务结算、报表统计、用户分析等高频业务,专注提升 SQL 存储过程的执行效率与稳定性。通过标准化的诊断流程,完成从问题定位—成因分析—优化方案—重构示例—收益评估—上线注意事项的全链路交付。在不改变业务逻辑的前提下,帮助团队显著缩短响应时间、降低资源消耗、减少扩容支出,并沉淀可复用的优化规范与最佳实践。用户只需粘贴存储过程代码、说明优化目标与数据库类型,即可获得定制化、可直接落地的优化建议与代码片段。最终实现性能与成本的双向提升,让关键流程跑得更快、更稳、更省钱。
请确认您是否已完成支付