事务
数据库中执行sql语句的最小工作单元, 不能拆分,执行同一业务的sql语句可以保证全部成功或全部失败
为什么使用事务?
如果不使用事务,客户端会自动提交,多次操作数据库做某一件事的时候(如:转账)会出现部分成功部分失败,则数据会出现异常.使用事务,把多次操作数据库的sql合并到一次提交中,这样就能保证同时成功或失败
事务的ACID特性 (面试)
- Atomicity: 原子性 事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
- Consistency: 一致性 事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账户金额之和在事务前后应该是保持不变的。
- Isolation: 隔离性,隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。也就是说,在事中务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
- Durability: 持久性,一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。
使用事务的过程
- 开启事务
- 执行多次sql (在内存中执行)
- 手动提交或(回滚/中断)
开启事务的三种方式
-
set autocommit=0;
关闭自动提交查看客户端自定提交的状态:
show variables like '%autocommit%';
关闭自动提交 仅对当前窗口生效
set autocommit=0;
打开自动提交
set autocommit=1;
-
start transaction;
(开始事务) -
begin
(开始事务)
案例 验证转账流程:
-
创建表
create table person( id int primary key auto_increment, name varchar(10), money int );
-
插入数据
insert into person values (null, '超人', 200), (null, '蝙蝠侠', 10000);
-
关闭自动提交 (3种方式都可以)
-- start transaction; -- begin; set autocommit=0;
-
转账
update person set money=500 where id=1; -- (+300) update person set money=9700 where id=2; -- (-300)
-
打开新窗口 验证是否转账成功?数据没变 因为两次操作都是在内存中操作 并未提交
-
手动提交
commit;
数据回滚
只能在事务中使用
执行rollback
会将数据回滚到上次提交的点
rollback;
使用quit
是中断事务, 和rollback
效果一样
设置回滚点
只能在事务中使用
- 保存回滚点:
savepoint s1;
s1是回滚点的名字 - 回滚到指定的回滚点:
rollback to s1;
验证回滚点:
- 先将回滚点500改成600
- 保存回滚点 s1
- 将超人从600改成700
- 回滚到s1 如果超人的钱 为600则说明成功!
总结
show variables like '%autocommit%';
查看自动提交状态set autocommit=0/1;
关闭或开启自动提交, 0是关闭, 1是开启start transaction;
开始事务begin
(开始事务)rollback
回滚在上次提交的点,只能在事务中使用quit
中断事务,只能在事务中使用savepoint s1;
自定义一个回滚点, 名字为s1,只能在事务中使用rollback to s1;
回滚带名字为s1的回滚点,只能在事务中使用commit;
提交事务,只能在事务中使用
扩展-事务并发读问题
多个事务对相同的数据同时进行操作,这叫做事务并发。
在事务并发时,如果没有采取必要的隔离措施,可能会导致各种并发问题,破坏数据的完整性等。这些问题中,其中有三类是读问题,分别是:脏读、不可重复读、幻读。
-
脏读(dirty read):在一个事务中,读取到另一个事务未提交更新的数据,即读取到了脏数据;
例如:A给B转账100元但未提交事务,在B查询后,A做了回滚操作,那么B查询到了A未提交的数据,就称之为脏读。
提示:需要将数据库的事务隔离级别设置为最低,才能够看到脏读现象 事务1:开启事务; A - 100 = 900; B + 100 = 1100; (没有提交事务) 事务2:开启事务; 查询B账户的金额 1100, 这个过程叫做脏读, 1100就是一个脏数据
-
不可重复读(unrepeatable read):对同一记录的两次读取结果不一致,因为在两次查询期间,有另一事务对该记录做了修改(是针对修改操作)
例如:在事务1中,前后两次查询A账户的金额,在两次查询之间,另一事物2对A账户的金额做了修改(并且也提交了事务),此种情况可能会导致事务1中,前后两次查询的结果不一致。这就是不可重复读。
事务1:开启事务--- 第一次读取A账户的金额:1000 第二次读取A账户的金额:900 事务2:开启事务--- A账户 - 100 = 900; 提交事务---
-
幻读(虚读)(phantom read):对同一张表的两次查询结果不一致,因为在两次查询期间,有另一事务进行了插入或者是删除操作(是针对插入或删除操作);
事务1:开启事务--- select * from acc where id=3;//不存在id为3的记录 insert into acc value(3,'C',2000); select * from acc where id=3;//存在id为3的记录 事务2:开启事务--- insert into acc value(3,'C',2000); 提交事务---
注意:mysql默认的是不允许出现脏读和不可重复读,所以在下面演示之前需要设置mysql允许出现脏读、不可重复读等。
set tx_isolation='read-uncommitted'; -- 设置mysql的事务隔离级别
扩展-事务隔离级别
事务隔离级别分四个等级,在相同数据环境下,对数据执行相同的操作,设置不同的隔离级别,可能导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力也是不同的。
查询当前事务隔离级别:
select @@tx_isolation;
设置隔离级别:
set tx_isolation='级别参数';
级别参数可取值4中, 如下:
-
READ UNCOMMITTED(读未提交数据)
安全性最差,可能出现任何事务并发问题(比如脏读、不可以重复读、幻读等)
但性能最好(不使用!!)
-
READ COMMITTED(读已提交数据)(Oracle默认)
安全性较差
性能较好
可以防止脏读,但不能防止不可重复读,也不能防止幻读;
-
REPEATABLE READ(可重复读)(MySQL默认)
安全性较高
性能较差
可以防止脏读和不可重复读,但不能防止幻读问题;
-
SERIALIZABLE(串行化)
安全性最高,不会出现任何并发问题,因为它对同一数据的访问是串行的,非并发访问;
性能最差;(不使用!!)
MySQL的默认隔离级别为REPEATABLE READ,即可以防止脏读和不可重复读
隔离级别总结
MySQL查询当前的事务隔离级别
select @@tx_isolation;
-
set tx_isolation='read-uncommitted';
安全性最差,容易出现脏读、不可重复读、幻读,但性能最高
-
set tx_isolation='read-committed';
安全性一般,可防止脏读,不能防止不可重复读、幻读
-
set tx_isolation='repeatable-read';
安全性较好,可防止脏读、不可重复读,但不能防止幻读
-
set tx_isolation='serialiable';
安全性最好,可以防止一切事务并发问题,但是性能最差。
JDBC设置事务隔离界别
JDBC中通过Connection提供的方法设置事务隔离级别:
Connection.setTransactionIsolation(int level)
leve参数可选值如下:
Connection.TRANSACTION_READ_UNCOMMITTED 1(读未提交数据)
Connection.TRANSACTION_READ_COMMITTED 2(读已提交数据)
Connection.TRANSACTION_REPEATABLE_READ 4(可重复读)
Connection.TRANSACTION_SERIALIZABLE 8(串行化)
Connection.TRANSACTION_NONE 0(不使用事务)
提示:在开发中,一般情况下不需要修改事务隔离级别