安全更新业务指标表需用START TRANSACTION+SELECT...FOR UPDATE锁源数据,再用INSERT...ON DUPLICATE KEY UPDATE写汇总表;必须加时间范围条件、确保汇总表主键唯一、避免SLEEP/大循环。

MySQL存储过程怎么写才能安全更新业务指标表
直接上结论:用 START TRANSACTION + SELECT ... FOR UPDATE 锁住源数据行,再用 INSERT ... ON DUPLICATE KEY UPDATE 写目标汇总表,避免并发重复计算或覆盖。
常见错误是直接 INSERT INTO summary SELECT ... FROM detail —— 没锁、没去重、没事务,跑两次就双倍累加。尤其在定时任务里,上一次没跑完,下一次又触发,问题当场爆炸。
- 必须给明细表加时间范围条件,比如
WHERE create_time >= DATE_SUB(NOW(), INTERVAL 1 DAY),否则每次全表扫,IO拉满还拖慢线上查询 - 汇总表主键要能唯一标识统计维度(如
(date, product_id, region)),否则ON DUPLICATE KEY UPDATE失效 - 别在存储过程中调
SLEEP()或大循环,MySQL不是应用层,卡住会阻塞整个连接池
怎么让存储过程每周一凌晨自动执行
靠 MySQL 原生事件调度器(Event Scheduler),不是 Linux cron 调 mysql -e "CALL ..." —— 后者失败不报错、权限难管、日志分散。
先确认调度器开着:SHOW VARIABLES LIKE 'event_scheduler',如果不是 ON,得在配置文件加 event_scheduler = ON 并重启,或者运行 SET GLOBAL event_scheduler = ON(临时)。
- 事件语法里
ON SCHEDULE EVERY 1 WEEK STARTS '2024-01-01 02:00:00',注意时区要和 MySQL server 一致(查SELECT @@time_zone) - 事件体里必须显式写
CALL your_summary_proc(),不能只写 SQL;如果过程有参数,得用变量中转,事件不支持传参 - 事件默认用 definer 权限执行,确保定义者账号对涉及的表有
SELECT和INSERT/UPDATE权限,否则静默失败
存储过程里遇到“Lock wait timeout exceeded”怎么办
本质是别的事务占着你要查的明细数据太久,比如某个长事务还在改订单表,你的汇总过程卡在 SELECT ... FOR UPDATE 上等了 50 秒超时。
别急着调大 innodb_lock_wait_timeout,先看谁在挡路:SELECT * FROM information_schema.INNODB_TRX 找 trx_state = 'LOCK WAIT' 的记录,再连 INNODB_LOCK_WAITS 看阻塞源头。
- 汇总过程自己别开太长事务:计算逻辑拆成小块,比如按天分批处理,每批用
COMMIT隔开 - 明细表记得在
WHERE条件字段(如create_time、status)建联合索引,否则FOR UPDATE会锁整张表 - 上线前用
EXPLAIN确认你的SELECT走的是索引,不是type: ALL
为什么定时跑出来的数据和手动算的对不上
大概率是时间窗口理解错:事件触发时刻是服务器时间,但业务上的“昨天”可能指自然日(00:00–23:59),而你用了 NOW() - INTERVAL 1 DAY,实际取的是过去 24 小时,跨了两个自然日。
更隐蔽的问题是 GROUP BY 字段含 NULL —— MySQL 默认把所有 NULL 归为同一组,但业务口径可能要求 NULL 单独计为“未填”或过滤掉。
- 自然日要用
DATE(NOW()) - INTERVAL 1 DAY,然后WHERE date_col >= '2024-04-01' AND date_col -
GROUP BY前加WHERE field IS NOT NULL显式过滤,或者用COALESCE(field, 'unknown')统一兜底 - 检查时区:应用写入用的是
UTC,但 MySQL server 是SYSTEM(东八区),NOW()和date_col可能差 8 小时
最麻烦的其实是业务逻辑变更后忘了同步改存储过程——没人审计它,也没单元测试,出问题只能翻 git log 对比。


















