MySQL 事务管理
事务的4个特性(ACID)
- 原子性 Atomicity。每个事务中的操作,要么都成功,要么都失败
- 一致性 Consistency。事务执行前后,数据库中的数据应该保持一致
- 隔离性 Isolation。事务之间应该是隔离的,事务之间互不影响、干扰
- 持久性 Durability。事务一旦提交,便会将修改持久化到数据库
事务管理的相关命令
start transaction; #开始事务
sql语句1;
sql语句2;
.....
commit; #提交事务
#rollback; #回滚
流程示例
start transaction;
insert into tb_user (name,age) values ('zhangsan',20); #显示Query OK,这条sql语句可以成功执行
insert into tb_user (name,age) values ('李四','age'); #显示ERROR,发生错误。只是说这条语句执行错误,但后面还可以执行sql语句
insert into tb_user (name,age) values ('wangwu',20); #显示Query OK,
如果此时commit;提交,会把执行成功的2个sql语句做的修改持久化数据库,失败的sql语句则忽略。
其实发生ERROR时,为了做到事务的原子性,就应该rollback;回滚。
并不是说start transaction; ..... commit; 会自动维持事务的4个特性,全部成功就持久化到数据库,某些sql语句发生错误,就自动回滚,不是这样的。
事务的4个特性需要我们自己来维持。
代码中使用事务也一样:
...... //开启事务
try{
..... //要执行的多条sql语句
commit; //若前面都正常执行,则提交
}catch(Exception e){
rollback; //发生错误就回滚
}
先开启事务,把要执行的多个sql语句放到try中,都正确执行那就提交,发生错误就回滚。
catch就是对异常的处理,既然捕获了异常进行了处理,就不会往上一级抛了。
如果每一级都是throws往上抛,抛到JVM,JVM默认的异常处理方式是:打印异常信息,终止程序运行。
比如说下面这段代码:
System.out.println(1); try{ System.out.println(1/0); }catch (Exception e){ System.out.println("error"); } System.out.println(2);
1 error 2
并不会自动在控制台打印异常信息,终止程序。
要看异常信息,可以在catch中 System.out.println(e.getMessage()); 打印出来
事务的并发
事务的并发即同时执行多个事务,主要涉及事务的隔离性、隔离级别。
1、事务并发执行可能出现的问题
(1)脏读 一个事务读取其它事务尚未提交的数据
事务B对数据库做了修改(执行成功但尚未提交),事务A读取这些已修改的记录,A读取之后,B进行了回滚,A读取到的数据变成了脏数据、无效数据,即脏读。
隔离级别 Read commited 已提交读,可解决脏读问题,等到使用这些记录的事务提交后才读取数据。
(2)不可重复读 前后多次读取,读取的数据内容不一致(期间进行了update操作)
比如说我卡里有2000,打算在ATM上取款2000,输入取款金额2000点击确定,系统查询账户余额,还有2000,是够的;
我老婆的微信绑定了我的卡,这时把我卡里的1000给花了,账户余额为1000
ATM执行扣款-2000(在账户余额的基础上扣,money-2000,又要获取账户余额),并往外吐钱2000,1000-2000=-1000
ATM前后2次读取余额(同一条记录的数据),读取的数据内容不一致,这就出现问题了。
隔离级别 Repeatable read 重复读,可解决不可重复读的问题,当有事务读了某些行的数据后,这些行会被锁住,不允许其它事务对这些行进行修改,这样重复读取到的数据就是一致的。
因为使用某些行时,这些行会被锁定,其它事务不能读取这些(可能会被修改的)行,也避免了脏读问题。
(3)幻读(虚读) 前后多次读取,读取的记录数不一致(期间进行了insert、delete操作)
隔离级别 Serializable 可解决幻读问题,不允许事务并发,最安全,但性能最差,基本不用。
以上3个是读问题,还可能产生一个写问题:丢失更新。
(4)丢失更新 丢失更新是不可重复读中的一种特殊情况,2个事务都要修改记录内容(update),后提交的覆盖了前面提交的
时间 | 取款事务 | 支票转账事务 |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100,把存款余额改为900元 | |
T6 | 提交事务 | |
T7 | 汇入100元,把存款余额改为1100元 | |
T8 | 提交事务 |
-100的更新操作丢失了。
2、4种隔离级别
- Read uncommitted:未提交读,解决不了任何读问题,安全性最低,但事务执行效率最高
- Read committed:已提交读,解决了脏读,但不可重复读、虚读有可能发生。oracle默认值。
- Repeatable read:重复度,解决了脏读、不可重复读,但虚读有可能发生。mysql默认值。
- Serializable:串行化,不允许事务并发,可解决所有并发问题,安全性最高,但事务执行效率最低
一般折中选择第2、3项,使用默认的即可,不必进行设置。
3、查看、设置隔离级别
#mysql 5
select @@tx_isolation; #查看当前会话使用的隔离级别
select @@global.tx_isolation; #查看全局设置的隔离级别
#mysql 8
select @@transaction_isolation; #查看当前会话使用的隔离级别
select @@global.transaction_isolation; #查看全局设置的隔离级别
#mysql5、mysql8的查看方式不同,5是tx,8是transaction,如果不对应会报错Unknown system variable 'tx_isolation'|'transaction_isolation'
#设置事务的隔离级别,5、8一样
set session transaction isolation level repeatable read; #设置当期会话使用的隔离级别为repeatable read
set global transaction isolation level repeatable read; #设置全局使用的隔离级别是repeatable read
如果我们没有使用start transaction开启事务,默认把一条sql语句(带分号)作为一个事务处理,会自动提交。