KES 定时任务与作业调度:sys_cron扩展、作业配置与自动化运维
前言
定时任务是数据库运维里省人力的关键手段,很多DBA头疼的就是那些重复性的日常操作:日志清理、统计信息更新、历史数据归档,全靠人肉执行的话,既容易忘,也占时间。
去年完成的一个自动化改造项目,让我对KingbaseES的定时任务能力有了更全面的认识。整个改造过程中,通过部署sys_cron扩展、梳理日常作业清单,DBA每周节省了大约6小时的手工操作时间,漏执行的事故降到了零。作业创建、调度配置、执行监控这些环节,KES都有完整的方案。
这篇文章,我想把自己在作业调度方面使用KES的经验分享给大家,重点讲解作业怎么创建、怎么管理、失败了怎么排查。希望能给还在手工跑脚本的朋友一些参考。
一、sys_cron扩展安装
定时任务在KES里靠sys_cron扩展实现,先把扩展装好,后面的作业都基于它。
安装与启用
扩展装在每个库上,装一次就能用。
sql
-- 先确认扩展可用(默认随数据库一起发布)
SELECT * FROM sys_available_extensions WHERE name LIKE '%cron%';
-- 在目标数据库里创建扩展
CREATE EXTENSION sys_cron;
-- 验证装好了
SELECT extname, extversion FROM sys_extension WHERE extname = 'sys_cron';
装完扩展后,作业元数据存在sys_cron模式下的几张表里。有个细节要注意:作业默认以创建者的权限执行,建作业用什么账号,作业就有什么权限。生产环境我习惯专门建一个cron_user账号,只授需要的权限,别图省事用超级用户。
调度语法
sys_cron用的是标准cron表达式,五段式,第一次接触的话对着表写就行。
sql
-- 典型的调度时间,对着改就行:
-- 每天凌晨2点 0 2 * * *
-- 每小时第30分钟 30 * * * *
-- 每周一早上6点 0 6 * * 1
-- 每月1号零点 0 0 1 * *
-- 每5分钟一次 */5 * * * *
-- 简写语法也支持,不想记cron符号的用这个:
-- 每分钟 '1 minute'
-- 每小时 '1 hour'
-- 每天一次 '1 day'
-- 每周一次 '1 week'
二、作业创建
装好扩展,接下来把日常操作一条条建成作业。
基础作业
数据清理类任务最适合先上手。
之前接手过一个库,日志表三年没人清理,膨胀到900多GB,磁盘三天两头报警。建了清理作业之后,磁盘占用稳定在60GB左右,再没报警过。
sql
-- 每天凌晨2点清理30天前的日志
SELECT cron.schedule(
'job_clean_logs', -- 作业名
'0 2 * * *', -- 调度时间
$$DELETE FROM operation_log WHERE log_time < CURRENT_TIMESTAMP - INTERVAL '30 days'$$
);
-- 查看已创建的作业
SELECT jobid, schedule, command, database, username, active
FROM cron.job;
作业创建函数返回一个jobid,后面管理作业全靠它。创建时建议都起个一眼能看懂的名字,后面排查问题的时候,"job_clean_logs"比"job_15"友好太多。
多类型作业
运维里的高频操作,基本都能建成作业。
sql
-- 每天凌晨3点更新统计信息(慢查询的常见原因就是统计信息过期)
SELECT cron.schedule('job_analyze', '0 3 * * *',
$$ANALYZE$$);
-- 每周日凌晨做历史数据归档
SELECT cron.schedule('job_archive_orders', '0 4 * * 0',
$$INSERT INTO orders_history SELECT * FROM orders
WHERE order_date < CURRENT_DATE - INTERVAL '90 days'$$);
-- 每天导出前一天的汇总数据给业务方
SELECT cron.schedule('job_daily_report', '30 6 * * *',
$$COPY (SELECT * FROM daily_summary WHERE stat_date = CURRENT_DATE - 1)
TO '/data/report/daily_report.csv' WITH (FORMAT csv)$$);
-- 归档完顺手把主表里的旧数据删掉(每周日凌晨,业务最低峰)
SELECT cron.schedule('job_purge_orders', '30 4 * * 0',
$$DELETE FROM orders WHERE order_date < CURRENT_DATE - INTERVAL '90 days'$$);
三、作业管理
作业建好不是就完事了,日常的查看、调整、停用都少不了。
查看与调整
作业信息集中在cron.job表里,改调度时间直接UPDATE。
sql
-- 看全部作业和状态
SELECT jobid, jobname, schedule, active, database, username
FROM cron.job
ORDER BY jobid;
-- 调整调度时间(比如清理任务从凌晨2点改到1点)
UPDATE cron.job SET schedule = '0 1 * * *' WHERE jobname = 'job_clean_logs';
-- 临时停用(保留配置,先不跑)
UPDATE cron.job SET active = false WHERE jobname = 'job_daily_report';
-- 重新启用
UPDATE cron.job SET active = true WHERE jobname = 'job_daily_report';
-- 彻底删除作业
SELECT cron.unschedule('job_clean_logs');
停用和删除的区别要记牢:active设成false只是暂停,作业配置还在,随时能启回来;unschedule是真删了,想恢复只能重建。版本发布窗口我一般把作业批量停用,窗口结束再统一启用。
执行记录
每次作业跑完都有记录,排查失败全靠它。
sql
-- 最近的执行记录(重点看status和return_message)
SELECT jobid, status, return_message, start_time, end_time
FROM cron.job_run_details
ORDER BY start_time DESC
LIMIT 20;
-- 只看失败的
SELECT jobid, status, return_message, start_time
FROM cron.job_run_details
WHERE status <> 'succeeded'
ORDER BY start_time DESC
LIMIT 10;
-- 结果示例:
-- jobid | status | return_message | start_time
-- 5 | failed | division by zero | 2026-09-28 02:00:03
-- 5 | succeeded | 1 row deleted | 2026-09-27 02:00:01
-- 3 | succeeded | UPDATE 1000000 | 2026-09-28 03:00:05
return_message这一列特别有用,作业里的SQL报了什么错,原文都在里面,多数问题看一眼就知道原因。比如上面jobid为5的作业,"division by zero"说明SQL里有除零问题,直接定位到代码。
四、失败处理与监控
作业没人盯着跑,失败通知机制必须建起来。
失败原因排查
常见的失败就那么几类,各有各的解法。
sql
-- 情况1:作业依赖的表被改名/删除,作业里SQL失效
-- 排查:看return_message里的报错对象名
SELECT return_message, start_time FROM cron.job_run_details
WHERE jobid = 5 AND status = 'failed' ORDER BY start_time DESC LIMIT 3;
-- 情况2:作业执行账号权限被回收
-- 排查:手工切到该账号执行同样的SQL试试
SET ROLE cron_user;
SELECT COUNT(*) FROM operation_log;
-- 情况3:作业和备份窗口撞车,锁等待超时
-- 处理:错开调度时间,避开备份和批处理高峰
UPDATE cron.job SET schedule = '0 5 * * *' WHERE jobname = 'job_analyze';
踩过一次印象很深的坑:客户反馈统计作业一直没生效,报表还是慢。查了半天发现作业时区配置不对,设置的凌晨3点实际跑在了中午业务高峰------作业本身成功了,但ANALYZE在高峰期跑反而添乱,还被DBA当成异常连接杀掉过。时区这事,装完扩展第一件事就该确认。
失败告警
作业失败的记录配合监控查询,就能做主动通知。
sql
-- 供监控平台采集的失败作业统计
SELECT
jobid,
COUNT(*) AS fail_count,
MAX(start_time) AS last_fail_time
FROM cron.job_run_details
WHERE status = 'failed'
AND start_time > CURRENT_TIMESTAMP - INTERVAL '1 day'
GROUP BY jobid
HAVING COUNT(*) > 0;
-- 连续失败的作业更要重视(偶发失败和持续失败是两码事)
WITH recent_runs AS (
SELECT jobid, status,
ROW_NUMBER() OVER (PARTITION BY jobid ORDER BY start_time DESC) AS rn
FROM cron.job_run_details
)
SELECT jobid, status FROM recent_runs
WHERE rn <= 3 AND status = 'failed'
GROUP BY jobid, status
HAVING COUNT(*) = 3;
监控平台轮询第一条查询,捞到数据就触发告警。第二条更狠一点,连续三次失败才报,过滤掉偶发抖动,我们生产上用的是这个策略,告警量少了但每条都值得看。
五、实战案例
去年完成的一个自动化改造项目,很好地验证了KES定时任务能力的价值。
项目背景
一个保险公司的业务系统,数据量8000万+。运维团队只有两个DBA,日常的日志清理、归档、统计信息更新全靠手工脚本,每周花在重复操作上的时间超过10小时,还出过两次漏执行导致的故障。
改造过程
通过梳理运维现状,发现当时的问题集中在三类:
- 日志清理、统计信息更新靠人工提醒,忘过好几次
- 历史数据归档每月手工执行,占用周末时间
- 作业失败没人知道,都是业务受影响后才发现
针对这些问题,我们做了改造:
- 部署sys_cron扩展,建专用执行账号
- 清理、归档、ANALYZE等6项操作全部建成作业
- 接入监控告警,失败主动通知值班人
改造效果
改造后,运维效率明显改善:
- DBA每周节省手工操作时间:约6小时
- 漏执行事故:全年为零
- 作业失败响应时间:从天级降到分钟级
bash
# 改造项目数据
# 数据量:8000万+
# 作业数量:6个
# 改造时间:3天
# DBA每周节省:6小时
# 漏执行事故:0
# 磁盘占用:从920GB降到60GB(日志清理作业)
客户反馈,最直观的变化是磁盘报警消失了,DBA终于能把时间花在性能优化这些有价值的事情上,而不是每天惦记着跑脚本。
总结与展望
通过实际项目的应用,KES在定时任务与作业调度方面确实有自己的优势。sys_cron扩展安装简单,作业管理方便,执行记录完整。特别是对于人手紧张的运维团队,把重复操作作业化是最划算的投入。
对于还在手工跑脚本的朋友来说,建议从日志清理这类最简单的作业入手,跑稳了再逐步把归档、统计信息更新这些迁过来。作业化的节奏可以慢,但方向值得坚持。
当然,每个系统的运维习惯不一样,作业清单也要结合自己的实际来定。但从经验来看,失败告警这一环千万别省,作业跑失败了没人知道,等于没做。
如果你也在做运维自动化,欢迎交流讨论,分享经验。