一、介绍
1、概念
事务指的是逻辑上的一组操作,组成这组操作各个逻辑单元要么全都成功,要么全都失败。MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在转账交易当中,甲转给乙100元,那么甲的账户要减少100,同时乙的账户增加100.
2、Mysql中的事务
- 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
- 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
- 事务用来管理 insert,update,delete 语句
3、事务的四大特点
原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。
4、事务的隔离级别
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
5、不同隔离级别带来的问题
脏读:A,B两事务,A事务会读取到B事务未提交的数据,然后B因为某些原因回滚数据,所以A就读取了B没有提交的数据,也称脏数据。
不可重复读:在A事务中对同一数据两次查询不一致,可能原因是在A事务提交之前B事务对该数据进行了操作
幻读:类似于不可重复读,都是在一个事务周期内读的数据不一致,区别在于幻读是侧重于插入操作带来的影响,而不可重复读是编辑或者删除带来的影响
二、事务案例
1、数据准备
use mydb;
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values (null,'a',10000);
insert into account values (null,'b',10000);
insert into account values (null,'c',10000);
2、代码模拟转账
@Test
public void transferAccounts() {
Connection conn = null;
PreparedStatement pstmt = null;
try {
/**
* 完成转账代码:
* * 扣除某个账号的钱
* * 给另外一个账号加钱
*/
// 获得连接:
conn = JDBCUtil.getConnection();
// 编写SQL语句:
String sql = "update account set money = money + ? where name = ?";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
// 用aaa账号给bbb账号转1000元
pstmt.setDouble(1, -1000);
pstmt.setString(2, "a");
// 执行SQL:扣除aaa账号1000元
pstmt.executeUpdate();
//int i = 1 / 0;
// 给bbb账号加1000
pstmt.setDouble(1, 1000);
pstmt.setString(2, "b");
pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtil.release(pstmt, conn);
}
}
由于在转账的过程中出现了程序异常,出现a账号的钱被转丢了,但是b账号的钱没有任何变化,所以通过事务来控制
三、Mysql两种事务提交方式
-
用 BEGIN, ROLLBACK, COMMIT来实现
BEGIN 开始一个事务
ROLLBACK 事务回滚
COMMIT 事务确认
直接用 SET 来改变 MySQL 的自动提交模式:
SET AUTOCOMMIT=0 禁止自动提交
SET AUTOCOMMIT=1开启自动提交
四、使用代码实现事务
@Test
public void transferAccounts() {
Connection conn = null;
PreparedStatement pstmt = null;
try{
/**
* 完成转账代码:
* * 扣除某个账号的钱
* * 给另外一个账号加钱
*/
// 获得连接:
conn = JDBCUtil.getConnection();
// 开启事务
conn.setAutoCommit(false);
// 编写SQL语句:
String sql = "update account set money = money + ? where name = ?";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
// 用aaa账号给bbb账号转1000元
pstmt.setDouble(1, -1000);
pstmt.setString(2, "aaa");
// 执行SQL:扣除aaa账号1000元
pstmt.executeUpdate();
int i = 1 / 0;
// 给bbb账号加1000
pstmt.setDouble(1, 1000);
pstmt.setString(2, "bbb");
pstmt.executeUpdate();
// 提交事务:
conn.commit();
}catch(Exception e){
// 回滚事务:
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
JDBCUtil.release(pstmt, conn);
}
}