若依Quartz踩坑记录

1,517 阅读2分钟

概述

前几天公司项目莫名部署出错, 今将相关数据脱敏后整理文章记录.

项目在 Docker 环境部署时启动失败, 报错信息 Table 'QRTZ_LOCKS' doesn't exist;, 提示 Quartz 相关表不存在导致项目无法启动, 且表名为大写.

部分报错信息:

Caused by: org.quartz.impl.jdbcjobstore.LockException: Failure obtaining db row lock: Table 'QRTZ_LOCKS' doesn't exist
	at org.quartz.impl.jdbcjobstore.StdRowLockSemaphore.executeSQL(StdRowLockSemaphore.java:184)
	at org.quartz.impl.jdbcjobstore.DBSemaphore.obtainLock(DBSemaphore.java:113)
	at org.quartz.impl.jdbcjobstore.JobStoreCMT.executeInLock(JobStoreCMT.java:238)
	at org.quartz.impl.jdbcjobstore.JobStoreSupport.clearAllSchedulingData(JobStoreSupport.java:2002)
	at org.quartz.core.QuartzScheduler.clear(QuartzScheduler.java:1547)
	at org.quartz.impl.StdScheduler.clear(StdScheduler.java:239)
	at com.lubanu.quartz.service.impl.SysJobServiceImpl.init(SysJobServiceImpl.java:42)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389)
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333)
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157)
	... 28 common frames omitted
Caused by: java.sql.SQLSyntaxErrorException: Table 'QRTZ_LOCKS' doesn't exist
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:121)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:916)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:972)
	at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeQuery(FilterChainImpl.java:3241)
	at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_executeQuery(FilterEventAdapter.java:459)
	at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeQuery(FilterChainImpl.java:3238)
	at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.executeQuery(PreparedStatementProxyImpl.java:175)
	at com.alibaba.druid.pool.DruidPooledPreparedStatement.executeQuery(DruidPooledPreparedStatement.java:213)
	at org.quartz.impl.jdbcjobstore.StdRowLockSemaphore.executeSQL(StdRowLockSemaphore.java:123)
	... 41 common frames omitted

原因分析

  1. 直接原因: Quartz 框架的常见问题
    1. Windows 连接 MySQL 时会启用大小写不敏感, 且建表时会将表名以及字段名转换为小写
    2. 基于 Quartz 的定时任务模块在首次连接到数据库时自动建表, 创建的表结构为小写
    3. Quartz 框架执行的 SQL 语句为大写, 在 Windows 上测试时, 默认为大小写不敏感所以并未出现问题
    4. 项目部署时, Linux 环境连接 MySQL 不会默认开启大小写不敏感, 导致 Quartz 框架执行的大写 SQL 语句无法匹配小写的数据库表, 发生报错
  1. 根本原因: 开发和部署环境不统一, 缺少测试
    1. 开发环境 (Windows) 与部署环境 (Linux) 连接 MySQL 时的默认配置不一致, 导致发生冲突
  1. 为什么旧版本没有问题: 旧版本没用到Quartz相关的表
    1. 旧版本的定时任务以内存模式运行, 没有使用 Quartz 相关表
    2. 在内存模式下, 多机器同时运行后端服务时会导致任务重复执行, 所以新版本添加了Quartz 的配置, 使用相关表进行分布式管理, 所以出现问题

修复措施

  1. 将服务器端的表结构改为大写, 与 Quartz 框架匹配; 或修改MySQL配置为大小写不敏感
  2. 环境一致性: 确保开发、测试和生产环境一致.
  3. 测试覆盖: 完善测试用例, 覆盖开发以及部署环境等不同场景.
  4. 文档记录: 将事故案例复盘并留档.

参考:

一个因MySQL大小写敏感导致的问题

编写日期: 2025年1月10日