mysql存储过程预汇总需用临时表、严格group by和生命周期管控;因only_full_group_by限制,非聚合字段须全出现在group by中;临时表应显式定义类型并建索引;jdbc调用需轮询多结果集;其硬伤包括memory引擎降级、无查询缓存、并发磁盘空间争抢。

MySQL存储过程能做报表预汇总,但必须配合临时表、明确的GROUP BY逻辑和可控的数据生命周期——直接在存储过程中塞一堆JOIN+聚合再返回结果集,90%会遇到性能崩盘或字段报错。
为什么GROUP BY在存储过程里容易报错
MySQL 5.7+默认开启sql_mode=ONLY_FULL_GROUP_BY,这意味着:SELECT里出现的非聚合字段,必须原样出现在GROUP BY子句中。很多人写完查询在命令行能跑,一塞进存储过程就报Expression #1 of SELECT list is not in GROUP BY clause。
- 错误写法:
SELECT user_id, DATE(order_date) AS dt, SUM(amount) FROM orders GROUP BY user_id—— 漏了DATE(order_date) - 正确写法:
GROUP BY user_id, DATE(order_date),不能用别名dt - 更稳妥做法:在存储过程中先用
CREATE TEMPORARY TABLE存中间结果,再对临时表聚合,避免嵌套表达式干扰GROUP BY解析
CREATE TEMPORARY TABLE是预汇总的核心载体
临时表只在当前连接生命周期内存在,适合做分步汇总:先按粒度粗筛,再逐层聚合,最后拼成宽表。比单条巨长SQL更易调试、加索引、控制内存使用。
- 必须显式指定列类型,避免隐式转换导致后续
SUM()精度丢失(比如用DECIMAL(12,2)而非FLOAT) - 临时表上建索引有效:
CREATE INDEX idx_user_dt ON temp_summary(user_id, report_date),尤其在二次JOIN时明显提速 - 不要依赖
DROP TEMPORARY TABLE IF EXISTS兜底——连接异常中断时临时表可能残留,建议开头先DROP再CREATE,并用DECLARE EXIT HANDLER捕获错误
Java调用时CallableStatement怎么处理多结果集
一个存储过程里执行多个SELECT,JDBC默认只返回第一个结果集。想拿到预汇总后的明细+统计值,得手动轮询。
- 调用
stmt.execute()后,用do-while循环 +stmt.getMoreResults()判断是否还有下一个结果集 - 每个结果集需单独处理:
try (ResultSet rs = stmt.getResultSet()) { ... },不能复用同一个ResultSet变量 - 输出参数(
OUT)只能在所有SELECT执行完后读取,顺序必须和registerOutParameter()一致,否则getString(3)可能取到空值
预汇总不是万能的,小心这三个硬伤
临时表+存储过程组合在千万级数据下仍可能卡住,根本原因不在SQL写法,而在MySQL引擎层限制:
- 临时表默认走
MEMORY引擎,但TEXT/BLOB字段或超大结果会自动降级为MyISAM,磁盘IO飙升 - 存储过程无法利用查询缓存(Query Cache已废弃),每次调用都是全新执行,无计划复用
- 并发调用同一存储过程时,各连接的临时表名相同但互不可见——这看似安全,实则掩盖了资源争抢:比如都往
temp_sales插数据,tmpdir磁盘空间可能瞬间打满











