数据库事务指的是一组数据操作或者说一组SQL操作,事务内的操作要么就是全部成功,要么就是全部失败,如果做了一部分但是只要有一步失败,就要回滚所有操作。
事务的ACID特性
-
原子性(Atomicity): 一个事务是一个单元工作,当中可能包括数个步骤,这些步骤必须全部执行成功,若有一个失败,则整个事务声明失败,事务中其他步骤必须撤销曾经执行过的动作,回到事务前的状态。MySQL中事务的回滚是通过回滚(Undo log)实现的,回滚日志记录的是你所有操作的逆操作,在需要回滚时,就把回滚日志里的操作全部执行一次。
-
一致性(Consistency): 事务作用的数据集合在事务前后必须一致,若事务成功,整个数据集合都必须是事务操作后的状态;若事务失败,整个数据集合必须与开始事务前一样没有变更,不能发生整个数据集合部分有变更,部分没变更的状态。
-
隔离行为(Isolation):在多人使用的环境下,每个用户可能进行自己的事务,事务与事务之间,必须互不干扰,用户不会意识到别的用户正在进行事务,就好像只有自己在进行操作一样。
-
持续性(Durability):事务一旦成功,所有变更必须保存下来,即使系统故障,事务的结果也不能遗失。这通常需要系统软、硬件架构的支持。
事务隔离级别
SQL标准定义了四种事务隔离级别,MySQL全都支持,分别如下:
-
读未提交(READ UNCOMMITTED)
-
读提交(READ COMMITTED)
-
可重复读(REPEATABLE READ)
-
串行化(SERIALIZABLE)
要了解这四个级别的影响,需要先了解多个事务并行时可能引发的数据不一致问题有哪些。
-
更新遗失。基本上就是指某个事务对字段进行更新的信息,因另一个事务的介入而遗失更新效力。解决办法就是设置“读未提交”隔离级别,这样事务A已更新但未提交的数据,事务B只能进行读取操作,但不能进行更新操作。
-
脏读。两个事务同时进行,其中一个事务更新数据但未提交,另一个事务就读取数据,就有可能发生脏读问题,也就是读到所谓脏数据、不干净、不正确的数据。如事务A更新了字段但未确认,事务B读取了,然后事务A撤回了,那么B读到的就不是提交的数据。要避免脏读,可以设置“读提交”隔离级别,也就是事务读取的数据必须是其他事务已提交的数据,如果存在未提交的更新事务,需要等待。
-
无法重复的读取 某个事务两次读取同一字段的数据并不一致。例如事务A在事务B更新前后进行数据的读取,则A事务会得到不同的结果,相当于事务A的两次读期间插入了一个更新字段的事务B。这种情况读提交级别就解决不了,此时需要设置“可重复读”隔离级别,也就是同一事务内两次读取的数据必须相同,这种级别下读取事务不会阻止其他读取事物,但是会阻止其他更新事务。
-
幻读 同一事务期间,读取到的数据笔数不一致。例如,事务A第一次读取得到五笔数据,此时事务B新增了一笔数据,导致事务B再次读取得到六笔数据。这种情况可重复读级别就解决不了,需要使用串行化级别,也就是在有事务时若有数据不一致的疑虑,事务必须可以按照顺序逐一进行。串行化情况下事务一个一个循序进行,对数据库的性能影响非常大。
各个事务隔离级别所解决的问题对比情况如下表:
隔离级别 | 更新遗失 | 脏读 | 无法重复的读取 | 幻读 |
读未提交 | 预防 | |||
读提交 | 预防 | 预防 | ||
可重复读 | 预防 | 预防 | 预防 | |
串行化 | 预防 | 预防 | 预防 | 预防 |
事务隔离级别越高并发事务就越安全,但是带来的性能开销也就越大,所以要根据系统需求进行选择,MySQL默认的隔离级别是可重复读取级别,Oracle则是读提交级别。
实现上,JDBC定义了五个事务隔离级别
-
TRANSACTION_NONE
-
TRANSACTION_READ_UNCOMMITTED
-
TRANSACTION_READ_COMMITTED
-
TRANSACTION_REPEATABLE_READ
-
TRANSACTION_SERIALIZABLE
其中TRANSACTION_NONE表示对事务不设置隔离行为,仅适用于没有事务、以只读功能为主、不会发生同时修改字段的数据库。有事务功能的数据库,可能不理会TRANSACTION_NONE的设置提示。如果要在JDBC层面使用事务,需要在执行SQL语句前关闭自动提交机制,执行完后手动调用commit()提交。这样中间执行过程中如果有SQL语句执行失败就会触发回滚。
conn.setAutoCommit(false); // default true
// start transaction block
// insert
// update
// if any errors within the start and end block,
// rolled back all changes, none of the statements are executed.
// end transaction block
conn.commit();
MySQL中事务隔离实现参考知乎文章MySQL中是如何实现事务隔离的