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) 原子性:一个事务里有很多条sql(修改操作),这些sql要么全部执行成功,要么全部执行失败,就像原子一样,不能再被分割
(2.2)一致性:事务执行完成后,数据库状态从一个一致的状态转换到另一个一致的状态,即执行事务之前状态满足约束条件,事务执行完成后状态满足约束条件。
例子:用户A给用户B转100,A-100,B+100。那是最终的A+B任然保持不变
(2.3)隔离性:就是每个事务是在不同的session中相互隔离、互不影响。在commit事务之前,修改操作不会提交到数据库。
session1的select操作不能读取到session2在commit之前修改数据库的结果,session1和session2仿佛隔离状态
但在session中update、delete会有加: 行锁 ,一定会等到其它session 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 默认级别 | 否 | 否 | 是 | 是 |
可序列化 | 否 | 否 | 否 | 否 |
这里讨论的"是、否" 都是同一事务的不同实例(session)。不同事务的不同实例(session)不在讨论范围:
在 repeatable read 隔离级别,多个线程,执行同一个事务,尽管会产生不同session,但不会出现胀读和不可重读情况
4 InnoDB的行锁(针对select)
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 数据库并发问题
(8.1)更新丢失:只有serializable级别可以完全避免更新丢失。账户余额并发修改:原有1000,扣两100钱,由于并发情况,第一次1000-100=900,第二次1000-100=900。那么第一次的减100的信息就丢失了。更新丢失需要靠应用程序保证。
第二类更新丢失:
在repeatable的级别时,
step1:
session1开启事务 读到账户=100,
step2:
session2开启事务 读到账户=100,
step3:
session2给账户增加100,修改账户=200,并提交。
step4:
可是尽管session2修改已经提交了,session1再次读取账户任然是100,而其他重新开启的事务读账户都是200(session1这时可能错误的认为了账户任然是100状态,满足条件,按原计划执行账户减少100操作:账户=0。也即可重复读级别)。
session1这时修改账户=0,那么覆盖了session2的修改(事实上我们希望账户是session1和session2共同修改的累加结果100)。
(8.2)胀读:session1更新记录1,但未提交。session2 select 记录1, 并根据搜索出来的记录1做后续操作,则造成未提交数据依赖关系问题,故称胀读。
(8.3)不可重复读:session1 读了记录1休息一下 ,session2 delete 记录1 ,这时session1再次读记录1,记录1不存在。
(8.4)幻读:幻读与不可重复读原理恰好相反,session1按照条件A查询,session2新插入数据,并且提交。session1按照条件1搜索出更多的果集
9 事务传播
以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更新不同表不会死锁,且事务独立