事务:一个包含多个步骤的业务操作。如果这个包含多个步骤的业务操作被事务管理,则这多个步骤要么同时成功(commit),要么同时失败(rollback)。
操作:
1. 开启事务:start transaction
2. 提交事务:commit
3. 回滚事务:rollback
银行转账业务存在问题的演示:
1、新建db1数据库,创建表格并插入数据
CREATE TABLE account ( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(10), balance DOUBLE ); -- 添加数据 INSERT INTO account (NAME, balance) VALUES ('Jack', 1000), ('Rose', 1000);
2、正常情况下,执行以下语句,发现转账正常
-- jack减500 UPDATE account SET balance = balance -500 WHERE NAME = 'jack'; -- rose加500 UPDATE account SET balance = balance +500 WHERE NAME = 'rose';
结果为:
3、如果在执行第一条SQL语句之后出现了异常,以下SQL模拟异常
-- jack减500 UPDATE account SET balance = balance -500 WHERE NAME = 'jack'; rose加500 UPDATE account SET balance = balance +500 WHERE NAME = 'rose';
即第一条SQL执行成功,第二条SQL由于异常没有执行
结果为:
使用事务管理来解决这个问题
转账之前开启事务,当使用了start transaction表示手动提交
-- 开启事务 START TRANSACTION; -- jack减500 UPDATE account SET balance = balance -500 WHERE NAME = 'jack'; rose加500 UPDATE account SET balance = balance +500 WHERE NAME = 'rose';
执行以上SQL,并没有提交,也没有回滚,现在这个事务还没有结束。此时在新的sqlyog窗口中查询,数据没有任何变化,如下图所示
但是在本sqlyog窗口中查询发现数据变化了,如下图所示,但是这些数据的变化时临时的变化,并不是持久的变化。当窗口关闭再打开,临时变化会被取消掉。
5、出了问题则回滚事务。这样数据库表中的数据没有发生变化,保证了账户的安全性
-- 开启事务 START TRANSACTION; -- jack减500 UPDATE account SET balance = balance -500 WHERE NAME = 'jack'; rose加500 UPDATE account SET balance = balance +500 WHERE NAME = 'rose'; -- 回滚事务 ROLLBACK;
6、发现执行没有问题,则提交事务
-- 开启事务 START TRANSACTION; -- jack减500 UPDATE account SET balance = balance -500 WHERE NAME = 'jack'; rose加500 UPDATE account SET balance = balance +500 WHERE NAME = 'rose'; -- 提交事务 COMMIT;
commit之后,数据发生了持久性的变化,事务随着commit的执行结束了。
Mysql数据库中事务默认自动提交(Oracle数据库默认是手动提交的),一条DML语句(增删改)会自动提交一次事务,例如执行以下SQL
UPDATE account SET balance = 1000
执行之后会默认提交一次事务,数据会被持久化更新,当执行了start transaction,就表示手动提交。
如果你开启了事务,没有提交事务,则事务默认会自动回滚。
手动提交需要start transaction,手动提交使用commit。DML语句不开启事务就会自动提交。
事务提交的两种方式:
1、自动提交:mysql就是自动提交的
2、手动提交:需要先开启事务,再提交
修改事务的默认提交方式:
1、先查看事务默认提交方式:1代表自动提交,0代表手动提交
SELECT @@autocommit;
结果如下:
2、修改默认提交方式
SET @@autocommit = 0;
如果此时执行DML语句而没有commit,sq语句是不会生效的,即还没有持久化保存。
Oracle数据库默认是手动提交的,将来用Oracle数据库,执行了增删改操作之后必须commit才会生效,如果没有commit,关闭窗口就会还原到之前的状态。
事务四个特性 ACID:
事务的隔离级别:
引发的问题:
四种隔离级别:
SELECT @@tx_isolation;
结果:
数据库设置隔离级别:
SET GLOBAL TRANSACTION ISOLATION LEVEL 级别字符串;
如:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
接下来演示脏读和不可重复读,通过设置不同的隔离级别来解决这些问题。
演示读未提交
1、打开一个cmd窗口,输入msyql -uroot -p123456,use db3;,select * from account;结果如下:
2、 设置隔离级别为读未提交,这样才能演示脏读的问题。
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
3、开启事务
start transaction;
4、再打开一个窗口,登录mysql,进入db3数据库,查询所有记录,给新窗口开启事务,这样,两个窗口都开启了事务。
5、执行转账操作
-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = 'jack';
-- rose加500
UPDATE account SET balance = balance +500 WHERE NAME = 'rose';
注意:并没有提交
6、新窗口查询所有记录
发现能够查询到旧窗口没有提交的数据,脏读的情况发生,隔离级别设置生效。
7、旧窗口执行rollback,回滚,数据会还原到转账之前的操作。此时新窗口再次查询所有记录,发现转账并没有成功,
此时也发生了不可重复读,在同一个事务中,两次读取的数据不一样。
脏读值一个事务还没有提交,另一个事务就能读取到没有提交的数据。
演示读已提交
设置隔离级别为读已提交,来解决脏读的问题
1、打开一个cmd窗口,输入msyql -uroot -p123456,use db3;,select * from account;结果如下:
1、将旧窗口事务的隔离级别设置为读已提交
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
3、关闭窗口,重新打开,开启事务
start transaction;
4、再打开一个窗口,登录mysql,进入db3数据库,查询所有记录,给新窗口开启事务,这样,两个窗口都开启了事务。
5、在旧窗口中执行转账的操作,
-- jack减500 UPDATE account SET balance = balance -500 WHERE NAME = 'jack'; -- rose加500 UPDATE account SET balance = balance +500 WHERE NAME = 'rose';
由于现在的隔离级别是读已提交,且还没有提交
6、此时在新窗口中查询所有记录,发现钱还没有到,只有当旧窗口的事务提交之后,真正的数据发生修改
7、旧窗口提交
commit;
8、新窗口查询所有记录,发现转账成功
但是还有一个问题,就是在同一个事务中,两次的查询结果不一致,即不可重复读。
有些需求要求在同一个事务中(事务没结束之前),每次读取的数据一模一样。我们希望在同一个事务中,每次查询的数据都是一样的。只有当这个事务结束之后,才会看到其他事务对这个表数据的修改情况。要完成这个需求,我们需要将事务的隔离级别设置成repeatable read
不可重复读指一个事务在提交前和提交后,另一个事务在没有结束之前读取到的数据不一样。
演示可重复度
设置隔离级别为可重复读,来解决不可重复读的问题
可重复读指事务结束之前可重复读。
1、打开一个cmd窗口,输入msyql -uroot -p123456,use db3;,select * from account;结果如下:
2、将旧窗口事务的隔离级别设置为读已提交
SET GLOBAL TRANSACTION ISOLATION LEVEL repeatable read;
3、关闭窗口,重新打开,因为设置了隔离级别之后,重新打开才会生效。此时事务的隔离级别为repeatable read,开启事务
start transaction;
4、再打开一个窗口,登录mysql,进入db3数据库,查询所有记录,给新窗口开启事务,这样,两个窗口都开启了事务。
5、在旧窗口中执行转账的操作,
-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = 'jack';
-- rose加500
UPDATE account SET balance = balance +500 WHERE NAME = 'rose';
由于现在的隔离级别是可重复读,还没有提交
6、在新窗口中查询所有记录,发现数据没有产生任何变化。
此时,右边的事务还没有提交,
7、提交旧窗口的事务,此时新窗口还是没有提交事务,新窗口再次查询所有记录,发现数据没有发生变化。说明,可重复读就生效了。
8、当把新窗口中的事务提交或回滚了之后,再次查询,才可以看到数据表的变化,
可重复读是指一个事务提交前和提交后,另一个事务在没有结束之前可重复读。
演示串行化
serializable:串行化就是一个锁表的动作,一个事务在操作数据表,另一个事务是不可以操作数据表的,
1、打开一个cmd窗口,输入msyql -uroot -p123456,use db3;,select * from account;结果如下:
2、将旧窗口事务的隔离级别设置为读已提交
SET GLOBAL TRANSACTION ISOLATION LEVEL serializable;
3、关闭窗口,重新打开,因为设置了隔离级别之后,重新打开才会生效。此时事务的隔离级别为serializable,开启事务
start transaction;
4、再打开一个窗口,登录mysql,进入db3数据库,查询所有记录,给新窗口开启事务,这样,两个窗口都开启了事务。
5、在旧窗口中执行转账的操作,
-- jack减500 UPDATE account SET balance = balance -500 WHERE NAME = 'jack'; -- rose加500 UPDATE account SET balance = balance +500 WHERE NAME = 'rose';
由于现在的隔离级别是串行化,还没有提交
6、在新窗口中查询所有记录,发现光标一直在闪,查询的动作并没有执行,只有当旧窗口事务提交或回滚之后,新窗口才能完成查询的动作,相当于这张表被锁住了。。
7、提交旧窗口的事务,新窗口立即回查询出来数据
串行化是将表锁住了,即一个事务在操作数据,其他事务无法操作相同数据。
使用Connection对象来管理事务
* 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
在执行sql之前开启事务
* 提交事务:commit()
当所有sql都执行完提交事务
* 回滚事务:rollback()
在catch中回滚事务
PlatformTransactionManager接口
我们自己写了一个事务管理器,spring提供了事务管理器,我们拿过来直接用就可以。
真正管理事务的对象
TransactionDefinition
事务的传播行为指什么情况下必须有事务(增删改必须有事务Required),什么情况下可有可没有(查询可有可没有事务supports)
只读:增删改read-only="false"
读写:只有查询方法才能用只读,read-only="true"
事务的隔离级别
使用数据库默认的隔离级别,mysql数据库的默认隔离几级别为可重复读,Oracle数据库的默认隔离级别为读已提交。