什么是事务?
数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
简单的说,事务就是将多条sql语句的执行绑在一起,要么都执行成功,要么都执行失败,即都执行成功才算成功,否则就会恢复到这堆SQL执行之前的状态。
例:张三转100块到李四的账户,这至少需要两条SQL语句:
- 张三账户余额减少100
- 李四余额加100
如果执行第一条sql给张三余额减少100的操作成功后程序出现了异常,导致执行第二条给李四余额减少100的余额失败了,那么程序执行后的结果是:张三余额减少了100,李四余额月没有加100。这样的结果显然是不合理的,张三也不会干。
所以这时候我们需要把这两条sql语句的执行绑在一起,当做一个事务执行,这样的话,如果给张三余额减少100的sql执行成功了,给李四的余额加100的操作却失败了,那么他们两的数据都会回滚到sql执行之前的状态,就是两个人的余额都不会变,这样才是合理的结果嘛。
实际上任何一条DML语句(insert、update、delete)的操作都是一个单独的事务,在MySQL中,默认情况下,事务是自动提交的,也就是说,只要执行一条DML语句就开启了事物,并且提交了事务。
例:在mysql里面执行一个更新操作,update table_name set column_name = xx
我们经常执行这样语句,可能这时看不出与事务有什么关系,但是其实mysql默认帮我们开启了事务,开始事务的语句:
- 开启事务:Start Transaction
- 事务结束:End Transaction
- 提交事务:Commit Transaction
- 回滚事务:Rollback Transaction
意思也就是说上面执行更新操作的sql也相当于:
Start Transaction;
update table_name set column_name = xx;
Commit Transaction;
只是说mysql默认帮我们开启和提交了事务,所以把多条sql的执行绑定在一起执行,就是说需要在Start Transaction和Commit Transaction中执行这多条语句,这样执行之后,如果中途出现异常,所有的数据都会回滚到sql执行钱的状态。
事务的四大特征
- 原子性(A):事务是最小单位,不可再分,事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。
- 一致性(C):事务要求所有的DML语句操作的时候,必须保证同时成功或者同时失败,比如张三向李四转账,不可能张三扣了钱,李四却没收到。
- 隔离性(I): 指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。也就是说,在事务中查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。例如:在A事务中,查看另一B事务(正在修改张三的账户金额)中张三的账户金额,要查看到B事务之前的张三的账户金额,要么查看到B事务之后张三的账户金额。
- 持久性(D):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚
事务的并发读问题
多个事务对相同的数据同时进行操作,这叫做事务并发。在事务并发时,如果没有采取必要的隔离措施,可能会导致各种并发问题,破坏数据的完整性等。这些问题中,其中有三类是读问题,分别是:脏读、不可重复读、幻读。
- 脏读(dirty read):读到另一个事务的未提交更新数据,即读取到了脏数据;
例:A给B转账100元但未提交事务,在B查询后,A做了回滚操作,那么B查询到了A未提交的数据,就称之为脏读。 - 不可重复读(unrepeatable read):对同一记录的两次读取不一致,因为另一事务对该记录做了修改(是针对修改操作)
例:在事务1中,前后两次查询A账户的金额,在两次查询之间,另一事物2对A账户的金额做了修改,此种情况可能会导致事务1中,前后两次查询的结果不一致。这就是不可重复度 - 幻读(虚读)(phantom read):对同一张表的两次查询不一致,因为另一事务插入了一条记录(是针对插入或删除操作);
事务的隔离级别
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。在事务的并发操作中,可能会出现上述并发读问题,下面通过举例说明。
-
READ UNCOMMITTED(读未提交)
指一个事务可以读取另一个未提交事务的数据。
例:
张三要给李四转账100块钱,要执行两条sql,一条是张三余额减100,第二条是李四余额加100,如果在执行完了第一条sql给张三余额减了100,还没有执行第二条sql给李四余额加100的时候,张三查看自己的余额发现自己的余额少了100,以为转账成功了,但是执行第二条sql的时候出错了,导致事务回滚了,所以实际上转账并没有成功。
分析:
实际上转账并没有成功,张三看到了事务未提交的数据,这就是脏读。
读未提交性能最好,安全级别最低, 可能出现任何事务并发问题(比如脏读、不可以重复读、幻读等),Read committed(读已提交),能解决脏读问题。 -
READ COMMITTED(读已提交数据)(Oracle默认)
指 一个事务要等另一个事务提交后才能读取数据
例:
张三卡里余额有100块,准备给李四转100块,他到达ATM开始操作(事务开始),首先ATM显示余额还有100块,足够转给李四,正在这个时候李四的女朋友网购需要100块正好提交了付款,这时ATM准备给张三的余额减去100块,再次检测余额的时候,发现余额就只有已经为0了。
分析:
这就是读已提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。
防止脏读,性能比REPEATABLE READ好,没有处理不可重复读,REPEATABLE READ可以处理这个问题。 -
REPEATABLE READ(可重复读)(MySQL默认)
重复读,就是在开始读取数据(事务开启)时,不再允许修改操作
例:
张三卡里余额有100块,准备给李四转100块,他到达ATM开始操作(事务开始),首先ATM显示余额还有100块,足够转给李四,正在这个时候李四的女朋友网购需要100块正好提交了付款,但是这个时候付款操作必须等待转账操作之后才能进行。
性能比SERIALIZABLE好
分析:
重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。 -
SERIALIZABLE(串行化)
不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;性能最差;
MySQL的默认隔离级别为REPEATABLE READ,即可以防止脏读和不可重复读
Spring事务的隔离级别
-
DEFAULT
默认隔离级别 -
READ_UNCOMMITTED
读未提交 ,对应MySQL的READ UNCOMMITTED。 -
READ_COMMITTED
读已提交 ,对应MySQL的READ COMMITTED。 -
REPEATABLE_READ
可重复读,对应MySQL的REPEATABLE READ。 -
SERIALIZABLE
串行化 ,对应MySQL的SERIALIZABLE。
Spring事务的传播机制
事务的传播机制表示当前事务方法被另一个事务方法调用时,指定事务应该如何传播。
-
PROPAGATION.REQUIRED
必须运行在事务中。当前存在事务,则在该事务中运行,不存在,则创建新的事务。 -
PROPAGATION.REQUIRED_NEW
必须运行在自己的事务中。无论存不存在事务,都会创建新的事务。当前事务会被挂起。 -
PROPATATION.SUPPORTS
单纯自己方法运行,不需要事务。如果当前存在事务,则在事务中运行,不存在,则不在事务中运行。 -
PROPAGATION.NOT_SUPPORTED
不运行在事务中。如果存在事务,当前事务将被挂起,自己非事务的运行。 -
PROPAGATION.MANDATORY
必须在事务中运行,如果不存在事务,抛异常。 -
PROPAGATION.NESTED
如果当前不存在事务,行为和PROPAGATION.REQUIRED一样。
当前存在事务,创建新的事务,嵌套在事务中(savepoint),外层事务失败,会回滚内层事务,内层事务失败,不会回滚外层事务。 -
PROPAGATION.NEVER
总是非事务的进行,如果当前存在事务,抛异常。