关于DolphinScheduler
Dolphin Scheduler是Apache组织开源的一款分布式易扩展的可视化工作流任务调度系统。适用于企业级场景,提供了一个可视化操作任务、工作流和全生命周期数据处理过程的解决方案。可以及时监控任务的执行状态,支持重试、指定节点恢复失败、暂停、恢复、终止任务等操作。
使用场景
我们利用它的工作流任务调度功能,定时执行SQL,可以对数据进行离线批处理操作。
在处理MySQL数据时,存在一种场景,需要先删除表数据,再将新加工的数据插入表中,这样存在一个问题,在数据插入之前,业务系统会查询到空数据,所以我们往往会使用事务控制,将删除和插入操作放在同一个事务中,比如这样:
BEGIN;
DELETE FROM table_a ;
INSERT INTO table_a SELECT * from table_b left join table_c ...;
COMMIT;
问题描述
在执行到INSERT INTO
这个过程的时候,查询出的数据重复,违反了主键约束
这个时候任务执行失败,不再继续往下执行,所以COMMIT
未执行。但是查询table_a
的时候发现表被清空了!
问题分析
在MySQL中,如果没有使用连接池,并且在 SQL 任务异常后没有执行 commit
或 rollback
,然后关闭了连接,数据库的行为取决于以下几个因素:
事务的行为
1. 自动提交(Auto-Commit)模式:
• 自动提交开启(默认):如果数据库连接的自动提交功能开启(通常 JDBC 默认是
auto-commit = true
),每个 SQL 语句都会被视为一个独立的事务并在执行后立即提交。因此,即使异常发生,如果已经执行的 SQL 语句没有显式的回滚操作,它们可能已经被提交,无法撤销。• 自动提交关闭:如果你手动关闭了自动提交(即
auto-commit = false
),所有 SQL 语句会在一个事务中执行,只有显式调用commit()
才会提交。如果在 SQL 任务过程中发生异常,并且没有手动执行rollback()
,再关闭连接时,数据库通常会自动回滚该事务。
2. 关闭连接后的行为:
• 无连接池的情况下,如果关闭连接且事务尚未提交或回滚,大多数数据库(包括 MySQL 和 Oracle)会自动回滚当前未提交的事务。这是因为关闭连接通常意味着客户端已经放弃了这个事务,数据库出于一致性考虑,会自动清理未完成的事务,防止脏数据写入数据库。
查询DolphinScheduler源码
找到执行SQL任务的代码,可以看出,任务执行结束后,会将连接关闭。
看一下close()
方法的注释:
这段注释解释了 JDBC Connection
类的 close()
方法的行为和最佳实践。以下是对每个部分的详细解释:
释放资源
/**
* Releases this <code>Connection</code> object's database and JDBC resources
* immediately instead of waiting for them to be automatically released.
*/
• 释义:调用
close()
方法会立即释放与Connection
对象相关联的数据库和 JDBC 资源。这意味着数据库连接和相关资源(例如语句和结果集)会被释放,而不是等待垃圾回收器或其他机制自动处理。
重复调用
/**
* Calling the method <code>close</code> on a <code>Connection</code>
* object that is already closed is a no-op.
*/
• 释义:如果你对一个已经关闭的
Connection
对象调用close()
方法,该方法不会产生任何实际效果,也不会抛出异常。这是一种“无操作”(no-op)行为,即它不会改变连接的状态或引发任何错误。
显式提交或回滚的推荐
/**
* It is <b>strongly recommended</b> that an application explicitly
* commits or rolls back an active transaction prior to calling the
* <code>close</code> method. If the <code>close</code> method is called
* and there is an active transaction, the results are implementation-defined.
*/
• 释义:强烈建议在调用
close()
方法之前,显式地提交commit()
或回滚rollback()
活动的事务。这是因为:• 如果在活动事务仍然存在的情况下调用
close()
,具体行为取决于 JDBC 驱动的实现。• 某些 JDBC 驱动可能会自动回滚未提交的事务,但这并不是标准行为。因此,确保在调用
close()
之前处理事务状态是最佳实践,以避免潜在的数据不一致或未定义的行为。
现在知道了,在执行到INSERT INTO
的时候出现异常,连接关闭,我们没有执行COMMIT
,也没有执行ROLLBACK
,具体行为取决于 JDBC 驱动的实现,通过在连接关闭前打印日志,发现auto-commit = true
rollback()
,看一下
rollback()
方法的注释有这样一句话
This method should be used only when auto-commit mode has been disabled.
rollback()
方法应该只在关闭自动提交模式 (auto-commit
) 时使用。
保险起见,在调用rollback()
之前将auto-commit
设置为false。
替换线上代码
使用Arthas不停机替换线上代码,具体方法可以看之前的文章: Arthas不停机替换线上代码
线上验证
事务正常回滚,delete操作被回滚,表数据没有被删除