定时任务应错峰执行、分片处理、用SKIP LOCKED抢任务、快进快出事务、按主键升序更新、用DB事件调度器。例如分散执行时间、小批次处理、SELECT ... FOR UPDATE SKIP LOCKED、移出耗时操作、ORDER BY id升序更新、启用EVENT调度。

定时任务执行时若直接操作数据库,很容易撞上事务锁——尤其在高并发或长事务场景下,轻则延迟、重则死锁。关键不是“避开锁”,而是让任务和锁“错峰”“分层”“可控”。
错开执行窗口,减少锁冲突概率
很多定时任务(比如凌晨清理、日终统计)集中在固定时间点触发,容易造成瞬时压力高峰。这时数据库连接池、行锁、间隙锁都可能被密集争抢。
- 把同类任务分散到不同分钟甚至不同小时执行,例如:原定每天00:00执行的5个任务,可分别设为00:02、00:07、00:13、00:19、00:25
- 对核心业务表的操作,尽量避开人工操作高峰(如工作日9:30–18:00),优先安排在夜间低峰期
- 若任务本身支持分片(如按日期、ID区间处理),可将单次大事务拆为多个小批次,每批加短事务+合理休眠,避免长时间持锁
用 SELECT FOR UPDATE SKIP LOCKED 抢任务,不等锁
当定时任务是“从队列表中捞待处理记录”这类典型场景(如异步消息消费、批量工单分发),传统 SELECT + UPDATE 容易引发竞争和阻塞。SKIP LOCKED 是 MySQL 8.0+ 和 PostgreSQL 的利器,能真正实现无等待调度。
- 写法示例:SELECT * FROM task_queue WHERE status = 0 ORDER BY id LIMIT 10 FOR UPDATE SKIP LOCKED
- 它会跳过已被其他事务锁定的行,只返回当前可安全获取的记录,彻底规避锁等待
- 配合应用层更新状态(UPDATE ... SET status = 1 WHERE id IN (...)),整个“拉取-标记-处理”链路原子、非阻塞、可横向扩展
事务内只做必要操作,快进快出
定时任务常因逻辑臃肿导致事务拖长——查数据、调外部接口、写日志、发通知全塞进一个事务里,锁住资源动辄几十秒。
- 把耗时操作(HTTP请求、文件IO、复杂计算)移出事务边界,只在事务内完成DB读写
- 读操作尽量用快照读(READ COMMITTED 隔离级别下 SELECT 不加锁),写操作前再加锁
- 批量更新时,按主键升序执行(如 UPDATE t SET x=1 WHERE id IN (1,3,5,2) → 改为 WHERE id IN (1,2,3,5)),避免多事务交叉加锁引发死锁
用事件调度器替代应用层轮询
有些定时任务本就不该由应用服务承担——比如清空日志表、归档历史数据、刷新物化视图。这类纯DB操作交给 MySQL 的 EVENT 调度器更轻量、更可靠。
- 启用方式:SET GLOBAL event_scheduler = ON;(需有 SUPER 权限)
- 创建示例:CREATE EVENT clean_logs ON SCHEDULE EVERY 1 DAY STARTS '2026-07-10 02:00:00' DO DELETE FROM sys_log WHERE create_time
- 优势:不占用应用线程、无网络开销、不受JVM GC或服务重启影响,且天然规避了应用节点间抢任务问题

















