1 事务针对范围:
(1)mysql的任何一个修改操作sql(insert update delete)事务都是客观伴随的,只不过默认是自动提交,这种情况,一旦执行sql,就自动commit了
(2)sql可以实现手动提交(关闭自动提交):
(2.1) start transaction / begin/ set autocimmit = 0
(2.2)执行update/insert/delete sql 但是没有提交到数据库
(2.3)commit 提交到数据库
2 事务特性
(2.1) 原子性:一个事务(代码表现形式是:@Transactional注解的方法)里有很多条sql(修改操作),这些sql要么全部执行成功,要么全部执行失败,就像原子一样,不能再被分割
(2.2)一致性:事务执行完成后,数据库状态从一个一致的状态转换到另一个一致的状态,即执行事务之前状态满足约束条件,事务执行完成后状态满足约束条件。
例子:用户A给用户B转100,A-100,B+100。那是最终的A+B任然保持不变
(2.3)隔离性:就是每个事务是在不同的事务之间相互隔离、互不影响。在commit事务之前,修改操作不会提交到数据库(持久化到磁盘)。
“已提交读”的隔离情况下,事务1的select操作不能读取到事务2在commit之前修改数据库的结果,事务1和事务2仿佛隔离状态
但在事务的update、delete会有加: 行锁 ,一定操作到的是真正commit的数据。(select不会加 行锁,故而胀读、不可重复读) 比如:
session1 | session2 |
begin set `status` = 3 where id = 'WK-BK-20171211163735578073203' and `status`= 1 affect num : 1 | |
update t_charge_record set `status` = 3 where id = 'WK-BK-20171211163735578073203' and `status`= 1 | |
这时,事实上这句update是任然存在事务(自动),由于session1对这行还没有的修改还没commit,所以mysql自身存在一个行锁,必须等session1提交后,才释放行锁 | |
commit session1释放行锁 | |
affect num : 0 由于session1提交后不满足where条件,返回受影响行数为0 |
(2.4)持久性
一旦成功commit后,数据就写到磁盘里面,持久化了,不可改变(妹的。。。把磁盘打碎算不算)
3 事务隔离级别(都是在讨论“读”)
胀读 | 不可重复读 | 幻读 | 更新丢失 | |
未提交读 | 是 | 是 | 是 | 是 |
已提交读 | 否 | 是 | 是 | 是 |
可重复度(repeatable read )mysql 默认级别 | 否 | 否 | 是 | 是 |
可序列化 | 否 | 否 | 否 | 否 |
胀读:事务1更新记录1,但未提交。事务2 select 到事务1尚未commit的记录1, 并根据记录1做后续操作,则造成未提交数据依赖关系问题(万一事务1回滚了更新记录1呢?)故称胀读。这种隔离级别的并发性最好。防止手段就是在事务1未提交事务的情况下,事务2是禁止读取记录的,当然也降低了并发性(我改的时候你不能读)
已提交读:事务1是无法select到事务2 还没有commit到数据库的更新,只有事务2 commit了后,事务1才能select到
可重复读:事务1 读了记录1 ,事务2 更新为记录1' ,且commit,这时事务1再次读记录1,记录1已经变成1'了,就叫做不可重复读。不可重复读,就是在这里需要做隔离,估计防止手段是在事务1读取记录1,而未提交的时候,禁止事务2修改记录1,同样也降低了并发性(我的时候你不能改)
幻读:事务1按照条件A查询,事务2新插入数据,并且提交。事务1按照条件A搜索出更多的果集。防止手段就是在事务1未提交的情况下,禁止插入新记录,这样并发性很差(我读的时候,你不能插)
第一类更新丢失(回滚丢失)
事务1 | 事务2 |
开始事务 | 开始事务 |
查询账户1000 | 查询账户1000 |
更新账户1000+100=1100 | |
commit | |
更新账户1000-100=900 | |
rollback | |
账户回滚到1000 |
事务1的回滚覆盖了事务2的更新,但在sql标准中,任何隔离级别都是不允许这种情况的,所以无需考虑
第二类更新丢失:
账户余额并发修改,账户原有1000,扣两次100钱,由于并发情况,第一次1000-100=900,第二次1000-100=900。那么第一次的减100的信息就丢失了。更新丢失需要靠应用程序保证。
注意:(1)在上面业务中,并发线程(事务)几乎同时select到原有账户余额,由于事务1此时并没有执行到update这句,两个事务都是在select,所以避免了脏读的隔离级别也不能解决这个问题。即防止脏读是防止:一个事务刚执行了update,但未commit,另一个事务的select该记录,会被阻塞,那个这个时间颗粒度是事务2的select到事务1的commt,并非整个事务中。
(2)避免了不可重复读的隔离级别也不能解决这个问题,尽管由于事务1还没有commit,事务2执行的update账户操作会被阻塞,但是等到事务2执行是,事务执行的基准任然是1000,并不是900(事务1扣100)。即防止不可重复读是防止:事务1执行过select,但未commit,事务2不能update,会被阻塞,阻塞的时间颗粒度也是事务2的update到事务1的commt,并非整个事务
第二类更新丢失:
4 InnoDB的行锁(针对select)
这里区分一个概念,隔离级别讨论的是读select的问题,而所有更新操作(update、insert、delete)都是需要获得锁的;
InnoDB有两种形式select行锁(注意:update/delete本身默认就会加行锁):共享锁、排他锁
(4.1)共享锁:select * from table lock in share mode
只是确保该条记录在整个事务中,不被修改(update delete),包括自己session和其它session。其它session修改会被阻塞,自己session修改会死锁
(4.2)排他锁:select * from table for update
保证在整个事务中,其它session不能修改此记录,且在自己session会修改此记录
5 乐观锁和悲观锁
乐观锁利用update/delete 操作本身行锁性质 + 返回受影响行数 + 版本号 实现
悲观锁利用排他锁实现
6 sql:begin,commit,rollback指令,与spring transaction 提交、回滚效果区别
7 事务中加java锁
8 事务传播
以service方法为例,f1方法是一个事务,f2方法是一个事务,f1中调用f2时,f2的事务和f1事务的关系?
(9.1)PROPAGATION_REQUIRED:如果f1存在事务,则f2加入f1的事务。
注意:spring默认事务传播,最常用!
(9.2)PROPAGATION_REQUIRES_NEW:无论f1是否有事务,f2都会新创建一个事务。
注意:这种级别有死锁情况,f1和f2都更新一个表,会死锁。f1和f2更新不同表不会死锁,且事务独立