资讯动态

QRTZ_LOCKS表不存在?一文解决JeecgBoot Quartz定时任务启动故障

发布时间:2026/10/13 11:49:37 来源:尧图企业网站定制
JeecgBoot启动提示QRTZ_LOCKS表不存在——这个问题我见过太多次了属于一看就慌、一查就明白的经典启动故障。新手看到控制台一堆红色堆栈第一反应是去翻代码、找配置折腾半天也摸不着头脑。实际上这个报错跟你的业务代码几乎没有关系问题大概率出在数据库这一层。这篇文章我会从JeecgBoot与Quartz调度器的关系讲起把QRTZ_LOCKS这张表为什么必不可少、为什么你的库里会缺它、以及怎么从根上解决统统说清楚。不管你是刚接触JeecgBoot做二次开发还是负责把项目往测试环境、生产环境部署看完这篇基本就能自己处理了。1. 先搞明白这个报错里的QRTZ_LOCKS到底是什么1.1 JeecgBoot与Quartz调度器的绑定关系JeecgBoot本身是一套快速开发平台业务表、菜单权限、报表这类能力都自带但它还有一个容易被忽略的模块——定时任务。你在后台界面上新配一个定时任务填好Cron表达式指定一个Java方法到点就能自动触发这个能力其实是Quartz调度器在背后干活。Quartz是Java生态里非常老牌的作业调度框架从任务注册、触发器管理到业务方法反射调用一套流程早就高度成熟JeecgBoot没必要自己从头写一遍直接整合进来是最省力的选择。既然要整合就绕不开一个关键问题任务跑起来之后这些任务定义放在哪里。Quartz给了两条路。一条是纯内存模式任务定义都放在JVM内存里应用一重启所有定时任务全部消失后台配置过的东西都得重新来一遍。另一条是JDBC持久化模式把任务详情、触发器、日历、调度锁这些统统落到数据库表里应用重启之后还能接着上次的状态运行。JeecgBoot在生产环境默认走的是JDBC模式这也是它能实现后台配置任务、重启不丢的原因。所谓JDBC模式核心就是Quartz约定了一套以QRTZ_开头的数据库表总共11张包括任务明细表、触发器表、Cron触发器表、Simple触发器表、调度器状态表、锁表等。Quartz在启动时会对这套表的完整性做校验任何一张缺失调度器都无法正常初始化。说得直白一点Quartz不会像某些框架那样缺哪张表自动建哪张它默认认为这些表应当存在如果不存在就会直接抛异常把Spring容器的启动过程打断。QRTZ_LOCKS表不存在就是我们今天要处理的这个报错的直接原因。1.2 一张锁表是如何影响启动的QRTZ_LOCKS这张表的结构非常简单核心字段就两个SCHED_NAME表示调度器名称LOCK_NAME表示锁名称联合主键由这两个字段组成。通常在初始化之后表里会有固定的5行数据对应Quartz内部的5类锁TRIGGER_ACCESS、JOB_ACCESS、CALENDAR_ACCESS、STATE_ACCESS、MISFIRE_ACCESS。光看字段你可能觉得这表也太简陋了跟业务数据完全不搭边。但它恰恰是Quartz整个调度机制的地基。Quartz在运行过程中的关键操作比如启动调度器、派发任务、修改触发器、处理错过执行的任务都需要先对QRTZ_LOCKS表中的特定锁记录执行SELECT ... FOR UPDATE。这句话会锁住对应那一行数据其他调度器实例想同时操作同一个任务时就必须在这里排队等待。换一种说法QRTZ_LOCKS就是Quartz的分布式互斥锁仓库它用数据库的行锁实现了同一时间只能有一个调度器在改动某个任务或触发器的保证。这里有个新手的认知盲区很多人以为只有集群部署才会用到QRTZ_LOCKS这张表。实际并不是。哪怕你的应用只有一个实例压根没有集群只要Quartz使用的是JDBC JobStore它也一样会去访问QRTZ_LOCKS。因为JobStore内部需要对任务状态做并发控制防止同一个触发器被线程池里的多个线程同时修改这跟单机还是集群没关系。所以启动流程走到JobStore初始化阶段Quartz就要开始查这张表了。表不存在SchedulerFactoryBean的初始化直接失败Spring容器起不来报错信息就落在控制台上了。看明白这条链路之后排查方向就很清楚了不用去改Java代码先确认数据库里的Quartz表是否存在、大小写对不对、配置里的表前缀和实例名是否匹配。2. 库表齐全却还报错可能性比你想的多2.1 初始化SQL没执行全这是最直接的原因大部分场景下的原因其实很朴素建库的时候只执行了JeecgBoot的业务表脚本比如菜单、用户、角色这些表都建出来了但定时任务模块需要的Quartz表被漏掉了。我见过不少项目开发者在本地把主SQL导入数据库就完事根本没注意到项目里还放着单独一份Quartz建表脚本。尤其是第一次接触JeecgBoot的人很容易被它初始化脚本不止一份的布局绊倒。业务表是一份脚本Quartz是一份脚本有时候还有流程引擎、报表模块的脚本少执行一份项目启动就会在这里栽跟头。判断方法很直接进数据库执行这条SQL看返回了多少张表SHOW TABLES LIKE QRTZ%;正常数量是11张QRTZ_JOB_DETAILS、QRTZ_TRIGGERS、QRTZ_SIMPLE_TRIGGERS、QRTZ_CRON_TRIGGERS、QRTZ_SIMPROP_TRIGGERS、QRTZ_BLOB_TRIGGERS、QRTZ_CALENDARS、QRTZ_PAUSED_TRIGGER_GRPS、QRTZ_FIRED_TRIGGERS、QRTZ_SCHEDULER_STATE、QRTZ_LOCKS。如果少了其中任何一张尤其是QRTZ_LOCKS根本不在列表里那结论就很明确初始化脚本没跑全把缺失的脚本补齐就能解决。有一点需要提醒不要只盯着QRTZ_LOCKS。很多人在报错提示里看到LOCKS表缺失就只建这一张表结果重启之后又报QRTZ_TRIGGERS不存在再建又报QRTZ_JOB_DETAILS不存在跟打地鼠似的。Quartz的启动检查是有顺序的第一张碰到的往往就是LOCKS表后续表也会逐一校验。所以正确做法是通盘检查11张表一次性补齐而不是缺哪张建哪张。2.2 表名大小写敏感本地没事部署就炸第二种原因最有迷惑性因为库表明明存在程序却找不到会让人怀疑人生。这里要交代一下MySQL的行为差异。默认情况下Windows和macOS上的MySQL表名大小写不敏感qrtz_locks和QRTZ_LOCKS会被当成同一个名字但Linux上的MySQL实例不少配置了lower_case_table_names0表名严格区分大小写。如果你在本地用Navicat导入脚本Quartz表实际建成了小写的qrtz_locks本地启动完全正常。一旦把应用打包部署到Linux服务器Quartz内部默认用大写表名进行查询MySQL会认为QRTZ_LOCKS是一张不存在的表于是抛出的错误信息跟手动删除表一模一样。这种本地正常、线上必炸的案例本质上是建表脚本和运行环境的命名风格不一致。处理方式有两种方向。一是建表时统一使用大写表名并把Quartz的tablePrefix配置保持大写QRTZ_二是调整MySQL的lower_case_table_names1让表名不再区分大小写。但改这个参数需要重启MySQL还会影响库里所有的大小写命名习惯风险并不小我不建议为了一张Quartz表去动全局参数。更稳妥的做法是让表名与Quartz默认的大写约定保持一致脚本一次性改对以后换环境也不怕。2.3 多数据源让你找错了库JeecgBoot自带多数据源能力这个设计在业务开发时很灵活但也可能成为Quartz报错的原因。Quartz初始化时它要连接的数据源不一定是你此刻正在操作的业务库。如果项目配置了多个数据源Quartz的JobStore默认取的是应用启动时的主DataSource。于是会出现一种诡异的现场业务功能全部正常菜单、用户、部门这些接口都通但启动日志里就是报QRTZ_LOCKS表不存在。为什么因为业务表建在主库Quartz表被DBA建到了另一个库或者连接串指向的Schema里根本没有执行过Quartz脚本。这种情况下的排查口诀是先对齐库再查表。翻一下配置里有没有spring.quartz.datasource这样的显式指定。如果没指定Quartz走的是默认主数据源那你就得确认主数据源连接的那个数据库里是不是真的存在QRTZ_LOCKS。很多开发者在多数据源环境下习惯性把注意力放在业务能不能查通上忽视了定时任务模块的底层连接导致这类问题隐蔽性很强。2.4 表前缀与实例名对不上还有一种原因纯粹是配置层面的跟建表本身没关系。Quartz有两个配置项与这张表紧密相关一个叫org.quartz.jobStore.tablePrefix控制Quartz查表时的前缀另一个叫org.quartz.scheduler.instanceName表示调度器实例名。前者如果被改成了业务前缀比如BIZ_QRTZ_而建表脚本里建的表名还是默认的QRTZ_开头程序就会去查BIZ_QRTZ_LOCKS库里面没有于是报表不存在。后者如果改过启动时可能不报错但任务真正执行时会发现QRTZ_LOCKS里的SCHED_NAME对不上锁记录找不到调度也会异常。我的经验是遇到QRTZ_LOCKS不存在这类报错先别急着执行建表脚本花两分钟把配置文件里的tablePrefix和实际库里的表名单对一遍往往能少走很多弯路。3. 解决思路从应急建表到自动初始化3.1 第一优先级把QRTZ相关表全部补齐处理这个报错的第一步也是最直接有效的一步就是把整套Quartz表一次性补齐。执行范围一定是全套11张而不是只建QRTZ_LOCKS。原因前面已经说过Quartz初始化对表的检查是有顺序有依赖的你只补LOCKS表后面大概率还会继续报别的表缺失。具体操作很清晰。打开JeecgBoot项目源码在db目录或docs目录下找Quartz的建表脚本文件名一般是tables_mysql_innodb.sql或quartz_mysql.sql。如果手头没有源码也可以直接到Quartz对应版本的jar包里找路径在org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql解压出来就是官方脚本。连上目标数据库把脚本整体执行一遍再回到第2章的验证SQL确认11张表都在最后重启应用。这里要特别注意一个细节官方脚本的默认逻辑是先DROP旧表再CREATE新表。如果你操作的生产库里已经存在部分QRTZ表直接执行官方脚本会把这些旧表连同里面存着的任务定义一起清空这个后果很严重。操作之前一定先备份。如果你的库里目前一张QRTZ表都没有那直接执行没有风险。为了降低误删概率我在下一章给了一套改造成CREATE TABLE IF NOT EXISTS的脚本重复执行也不会清数据适合先跑一遍试水。3.2 让应用启动时自动建Quartz表手工执行脚本解决了眼前的问题但如果你经常要搭建新环境这一步人工操作很容易遗漏。更省心的做法是让应用启动时自动把Quartz表建好。Spring Boot在集成Quartz之后暴露了一个初始化开关就是spring.quartz.jdbc.initialize-schema有三个值never表示从不自动建表embedded表示仅内嵌数据库才自动建表always表示无论什么数据库都尝试自动建表。把它设成alwaysSpring Boot启动时会根据当前数据库类型自动选择Quartz内置脚本执行。这套机制用起来很方便但也有需要留意的地方。Spring Boot初始化时一般会先判断表是否存在存在就跳过不过不同版本的具体行为不太一样。如果遇到表已存在的报错就把模式改回never改用手动脚本初始化。另外自动建表主要解决的是表的物理结构问题QRTZ_LOCKS表里的5行锁记录不一定会被自动写入。所以我自己的习惯是无论用哪种方式建表建完以后手动执行一遍锁记录初始化SQL确保调度器真正可用。还有一种常见做法是把建表脚本放到应用的resources目录下命名为schema.sql配置spring.sql.init.modealways。只要脚本里用的是CREATE TABLE IF NOT EXISTS这套方案可以做到重复执行不报错非常适合开发和测试环境自动拉起生产环境也建议配合备份一起用。3.3 RAMJobStore只能拿来应急如果你的目的只是快速把应用跑起来看一眼页面不关心定时任务的持久化和历史调度记录技术上确实有一条绕开所有QRTZ表的路把Quartz的JobStore切换成RAMJobStore。配置方式是在YAML文件里把spring.quartz.job-store-type改成memory或者直接把org.quartz.jobStore.class设成org.quartz.simpl.RAMJobStore。改完之后应用启动确实不再碰数据库QRTZ_LOCKS表也不需要了启动速度还快了不少。但代价同样明显。切换成内存模式后所有任务定义、触发器状态全部存在JVM里应用一重启任务全部归零后台配置过的定时任务要重新录入。生产环境如果这么干定时任务模块等于失去持久化能力哪一天应用意外重启所有周期性任务都会停摆排查起来非常头疼。所以我把这个方案定性为应急只建议在本地调试或者临时演示时用上生产前务必改回JDBC模式并补齐表结构。4. 动手实操可用的MySQL建表脚本与验证方法4.1 完整建表SQLMySQL InnoDB为了避免大家在各种版本之间反复折腾我贴一套基于Quartz 2.3.x官方MySQL InnoDB脚本改造的建表语句。这套脚本使用CREATE TABLE IF NOT EXISTS已存在的表不会被重建所以重复执行不会把已有数据冲掉。需要注意一点MySQL对CREATE INDEX没有提供IF NOT EXISTS语法如果整套脚本重复执行索引部分可能报Duplicate key name。解决办法是遇到索引重复的报错可以跳过索引段索引缺失只会影响部分查询性能不影响Quartz启动。首次建表执行全套则没有这个问题。-- Quartz 11张表MySQL InnoDB适合JeecgBoot场景 CREATE TABLE IF NOT EXISTS QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE VARCHAR(1) NOT NULL, IS_NONCONCURRENT VARCHAR(1) NOT NULL, IS_UPDATE_DATA VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) ) ENGINEInnoDB; CREATE TABLE IF NOT EXISTS QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT(13) NULL, PREV_FIRE_TIME BIGINT(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT(13) NOT NULL, END_TIME BIGINT(13) NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT(2) NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), KEY IDX_QRTZ_T_J (SCHED_NAME, JOB_NAME, JOB_GROUP), KEY IDX_QRTZ_T_JG (SCHED_NAME, JOB_GROUP), KEY IDX_QRTZ_T_C (SCHED_NAME, CALENDAR_NAME), KEY IDX_QRTZ_T_G (SCHED_NAME, TRIGGER_GROUP), KEY IDX_QRTZ_T_STATE (SCHED_NAME, TRIGGER_STATE), KEY IDX_QRTZ_T_N_STATE (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE), KEY IDX_QRTZ_T_N_G_STATE (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE), KEY IDX_QRTZ_T_NEXT_FIRE_TIME (SCHED_NAME, NEXT_FIRE_TIME), KEY IDX_QRTZ_T_NFT_ST (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME), KEY IDX_QRTZ_T_NFT_MISFIRE (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME), KEY IDX_QRTZ_T_NFT_ST_MISFIRE (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE), KEY IDX_QRTZ_T_NFT_ST_MISFIRE_GRP (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE), CONSTRAINT FK_QRTZ_TRIGGERS_01 FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP) ) ENGINEInnoDB; CREATE TABLE IF NOT EXISTS QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT BIGINT(7) NOT NULL, REPEAT_INTERVAL BIGINT(12) NOT NULL, TIMES_TRIGGERED BIGINT(10) NOT NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_01 FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ) ENGINEInnoDB; CREATE TABLE IF NOT EXISTS QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(120) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), CONSTRAINT FK_QRTZ_CRON_TRIGGERS_01 FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ) ENGINEInnoDB; CREATE TABLE IF NOT EXISTS QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_01 FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ) ENGINEInnoDB; CREATE TABLE IF NOT EXISTS QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), CONSTRAINT FK_QRTZ_BLOB_TRIGGERS_01 FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP) ) ENGINEInnoDB; CREATE TABLE IF NOT EXISTS QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (SCHED_NAME, CALENDAR_NAME) ) ENGINEInnoDB; CREATE TABLE IF NOT EXISTS QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP) ) ENGINEInnoDB; CREATE TABLE IF NOT EXISTS QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT(13) NOT NULL, SCHED_TIME BIGINT(13) NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT VARCHAR(1) NULL, REQUESTS_RECOVERY VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME, ENTRY_ID), KEY IDX_QRTZ_FT_TRIG_INST_NAME (SCHED_NAME, INSTANCE_NAME), KEY IDX_QRTZ_FT_INST_JOB_REQ_RECOVERY (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY), KEY IDX_QRTZ_FT_J_G (SCHED_NAME, JOB_NAME, JOB_GROUP), KEY IDX_QRTZ_FT_JG (SCHED_NAME, JOB_GROUP), KEY IDX_QRTZ_FT_T_G (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP), KEY IDX_QRTZ_FT_TG (SCHED_NAME, TRIGGER_GROUP) ) ENGINEInnoDB; CREATE TABLE IF NOT EXISTS QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT(13) NOT NULL, CHECKIN_INTERVAL BIGINT(13) NOT NULL, PRIMARY KEY (SCHED_NAME, INSTANCE_NAME) ) ENGINEInnoDB; CREATE TABLE IF NOT EXISTS QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME, LOCK_NAME) ) ENGINEInnoDB;这段脚本在建表顺序上先建任务明细表再建触发器表最后建锁表符合外键依赖关系。JeecgBoot项目里如果已经有了部分Quartz表这套脚本不会破坏已有表可以直接补建缺失部分。如果之前从未建过整套执行完就能得到完整的11张表。4.2 锁记录初始化与SCHED_NAME匹配表结构建好之后QRTZ_LOCKS还只是空壳里面一行数据都没有。而Quartz运行时要靠这5行锁记录来实现互斥操作。如果缺行数据表现出的症状跟缺表还不太一样启动可能顺利通过但任务调度执行阶段会报类似no rows found in QRTZ_LOCKS的异常。所以锁记录的初始化这一步不能省。默认情况下JeecgBoot的Quartz调度器实例名是schedulerFactoryBean这也是Spring Boot创建SchedulerFactoryBean时的默认bean名称。对应的锁记录插入语句是这样INSERT IGNORE INTO QRTZ_LOCKS (SCHED_NAME, LOCK_NAME) VALUES (schedulerFactoryBean, TRIGGER_ACCESS), (schedulerFactoryBean, JOB_ACCESS), (schedulerFactoryBean, CALENDAR_ACCESS), (schedulerFactoryBean, STATE_ACCESS), (schedulerFactoryBean, MISFIRE_ACCESS);这句SQL里的schedulerFactoryBean并不是一成不变的。如果你在application.yml里改过spring.quartz.scheduler-name或者配置了自定义的org.quartz.scheduler.instanceName就必须把这个值替换成你配置的实例名。判断当前实例名的方法也简单在项目配置目录里搜索instancename或scheduler-name关键字找到对应的值再看一眼即可。SCHED_NAME不匹配导致的错误很隐蔽但排查起来并不难记住锁记录的每一行都必须与Quartz的instanceName完全一致。4.3 验证Quartz表是否就绪的常规操作执行完建表和插入锁记录之后不要急着启动应用先在数据库里做一轮确认。我用这几条SQL做最终验证SHOW TABLES LIKE QRTZ%; SELECT COUNT(*) FROM QRTZ_LOCKS; SELECT * FROM QRTZ_LOCKS;第一条返回的应该是11条结果不多不少。第二条返回的应该是5表示锁记录已经就位。第三条能直观看到锁记录的具体名称和SCHED_NAME是否与配置一致。全部符合预期后重新启动JeecgBoot。如果启动日志里不再出现SQLSyntaxErrorException定时任务模块就能正常初始化了。这里再补一个小经验验证的时候别看Navicat左侧的树形列表凭感觉说表都在最好用SQL语句数一遍。因为Quartz表名和普通业务表混在一起视觉上很容易漏看尤其是有多个Schema时左侧列表可能默认停留在某个无关数据库上你看到的是另一个库的表。用SHOW TABLES LIKE这种方式查目标库里到底有没有一秒钟就有结论。5. 实战避坑几个常见的二次翻车现场5.1 只补LOCKS表后面还有十张表在等着有次一个开发者在群里发截图说他已经按照网上的教程手动建了QRTZ_LOCKS表结果重启还是报错。我让他执行一下SHOW TABLES LIKE QRTZ%他截图过来一共就一张表。这就是典型的见招拆招式处理带来的二次问题。Quartz初始化时每张表都有自己的职责任务明细表、触发器表、调度器状态表缺一不可。正确做法是开始时就把整套表补齐别跟报错信息打地鼠。5.2 改了表前缀忘了改配置还有一种情况是项目为了区分多租户或者多团队把Quartz表前缀统一加了个标识比如将默认的QRTZ_改成了TENANT_QRTZ_。建表脚本执行的时候库里的表名确实也是TENANT_QRTZ_LOCKS但配置文件中的tablePrefix没有同步修改Quartz仍然去查QRTZ_LOCKS自然找不到。这种问题最有迷惑性的一点在于你打开数据库一看Quartz表全都存在很难第一时间意识到是前缀没对上。我的建议是遇到这种问题直接在库里执行SHOW TABLES LIKE %LOCKS%先把实际表名看清楚再去反推配置定位就很快了。5.3 本地正常、生产报错的环境差异本地一切正常上了服务器就报QRTZ_LOCKS不存在这种现象在Linux部署时非常常见。根因就是我在第2章提到的大小写敏感差异。Windows开发机上MySQL默认表名不区分大小写小写qrtz_locks建表也能跑Linux实例一旦区分大小写Quartz用大写表名去查就找不到。处理思路就两条要么把表全部改成大写与Quartz默认的tablePrefix对齐要么重命名已有小写表。很多人只改了新建表脚本忽略了服务器上已经存在的那一批表导致问题反复出现。所以排查时要把已有表实际名称和新建脚本里的名称分开检查两边对齐才算真处理完。5.4 避坑清单速查表场景报错表象解决思路初始化脚本漏执行11张QRTZ表数量不足补齐全套建表脚本并插入锁记录表名大小写不一致本地正常Linux启动失败统一使用大写表名或重命名已有小写表多数据源指向错位业务正常定时任务初始化失败确认Quartz实际数据源连接的库中是否有QRTZ表表前缀没同步存在业务前缀表但程序仍找QRTZ对齐tablePrefix与建表脚本中的表名实例名不一致启动成功任务执行时锁异常锁记录中的SCHED_NAME与instanceName保持一致RAMJobStore应急应用重启后定时任务全部丢失生产环境必须切回JDBC JobStore最后再说点个人体会。JeecgBoot这类开发平台平时大家把精力都放在业务表、接口、前端交互上很少有人会专门去研究底层定时任务所依赖的Quartz表结构。但恰恰是这种框架自动管理的组件出问题的时候最难排查。我在这个报错上吃过几次亏之后已经养成一个固定习惯每次初始化一个JeecgBoot环境拿到初始化脚本后先整体看一眼一共几个文件业务表脚本执行完立刻检查QRTZ表确认11张表全部到位、锁记录5行数据齐全再开始启动应用。另外Quartz这套建表脚本我在团队内部做了统一归档谁需要搭新环境直接复制一份不用每次都在源码目录里翻来找去。如果你也在维护JeecgBoot项目建议趁早把这一步固化到部署清单里能省下不少在启动报错中摸爬滚打的时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑