如何实现Liquibase双重检查锁模式?无更新时不获取锁
Liquibase优化启动逻辑:无变更时跳过锁获取
完全可以实现你想要的逻辑——无新变更时跳过锁获取直接启动,既提升启动速度,又降低残留锁的概率。结合你用Spring集成Liquibase的场景,具体实现步骤如下:
1. 实现无锁预检查变更集的工具类
先写一个工具类,基于Liquibase API在不获取锁的前提下,检查是否有未执行的变更集。核心思路是直接对比本地变更文件和databasechangelog表的记录,避免触发默认的锁操作:
public class LiquibaseChangeChecker { public static boolean hasUnrunChanges(DataSource dataSource, String changeLogLocation) throws LiquibaseException { // 构建无锁的Liquibase实例 Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(dataSource.getConnection())); Liquibase liquibase = new Liquibase(changeLogLocation, new ClassLoaderResourceAccessor(), database); // 跳过锁操作,直接查询未执行的变更集 List<ChangeSet> unrunChanges = liquibase.listUnrunChangeSets(null, null); return !unrunChanges.isEmpty(); } }
2. 自定义条件注解判断是否需要迁移
创建一个@ConditionalOnLiquibaseChangesNeeded注解,结合Spring的Condition机制,在启动时自动判断是否需要执行迁移:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Conditional(LiquibaseChangesNeededCondition.class) public @interface ConditionalOnLiquibaseChangesNeeded { } public class LiquibaseChangesNeededCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { try { DataSource dataSource = context.getBeanFactory().getBean(DataSource.class); return LiquibaseChangeChecker.hasUnrunChanges(dataSource, "classpath:/db/changelog"); } catch (Exception e) { // 检查失败时默认执行迁移,避免漏更 return true; } } }
3. 优化SpringLiquibase Bean的创建逻辑
- 用自定义条件注解控制SpringLiquibase Bean的初始化:只有当预检查发现有未执行变更时,才创建并初始化SpringLiquibase,无变更时直接跳过。
- 扩展
SpringLiquibase类,在执行迁移前做二次检查,防止预检查后其他节点已经执行完变更,避免不必要的锁获取:
public class SafeSpringLiquibase extends SpringLiquibase { @Override public void afterPropertiesSet() throws LiquibaseException { // 二次确认是否有未执行变更 if (!LiquibaseChangeChecker.hasUnrunChanges(getDataSource(), getChangeLog())) { return; } super.afterPropertiesSet(); } }
- 配置Bean:
@Bean @ConditionalOnLiquibaseChangesNeeded public SpringLiquibase springLiquibase(DataSource dataSource) { SafeSpringLiquibase liquibase = new SafeSpringLiquibase(); liquibase.setDataSource(dataSource); liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); // 可选:配置锁重试参数,应对临时锁占用 liquibase.setLockRetryCount(5); liquibase.setLockRetryWaitTime(1000); return liquibase; }
4. 残留锁兜底处理
如果还是出现崩溃导致的残留锁,可以在工具类的预检查步骤中加入锁清理逻辑:检查databasechangeloglock表,如果锁记录存在且对应的节点标识(比如IP、进程ID)已失效,自动释放锁。
内容的提问来源于stack exchange,提问作者Andrey
相关产品推荐
相关产品推荐

