参考:官方文档
X排他锁,S共享锁,只有S锁和S锁的获取之间是不互斥的。
其规则:
事务获取X锁前,必须获取IX,获取S前,必须获取IS
IX,IS又被称为意向锁,该锁被设计的目的是:
考虑以下场景:
在一个事务中执行了select * … for update(添加IX锁,for share添加IS锁),并为其中某几条记录添加了行锁,
另外一个事务 执行了 lock table xx lock mode write(添加IX锁,);会检查IX锁,因为表级的X锁和IX锁是互斥的,所以无法获取
X,S分别有表级别和行级别之分,而IX,IS只有表级,
X IX S IS
X Conflict Conflict Conflict Conflict
IX Conflict Compatible Conflict Compatible
S Conflict Conflict Compatible Compatible
IS Conflict Compatible Compatible Compatible
行锁会在索引上加索,
以SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;为例,会在c1上加锁
锁住一段范围,防止对其中的insert操作(因为 insert也要获取gap锁,可以防止幻读)
且间隙锁不分X,S锁,他们都是不互斥的 ,事务 A获取了间隙锁,事务B也可以获取
举个 例子,切记id不能是主键,文档说明,当where条件加在唯一索引上(包括主键)是不会加gap锁 的
create table test(
id int,
t varchar(5),
key(id)
);
insert into test(id, t) values(10, "a"),(20, "b"),(30, 'c');
begin;
update test set t = '' where id = 20;
begin;
insert into test(id, t) values(11, "a"); -- 会直到抛出超时异常
begin;
insert into test(id, t) values(31, "a"); -- 正常
-- 说明了在10~30上都被加了锁
如果换成t呢?
begin;
select * from test where t = '' for update;
begin;
insert into test(id, t) values(50, 'ccc'); -- 仍然出现了问题,加上了table级的锁
同时文档中补充了,read commited只会不会使用gap锁,read uncommited更不会了,所以read commited是无法防止幻读的 ,至于 串行化隔离级别,无论你select是否 for update都会假设gap锁。
== gap lock + record lock (顺序不能乱)
还是举个例子
来自 博客
MVCC用于查询一些正在被其他更新的数据,用于 提高并发(原理:逻辑上删除事务id和创建事务id) – 即 创建版本和删除版本
select:1创建版本小于当前 事务(保证了事务之前行存在)2.删除版本undefined or 删除版本大于当前版本 事务开始的时候还没删除
insert:创建版本号 = 当前版本号
update:先加入,创建时间为当前版本,并把修改的数据删除版本设为当前版本
delete:删除版本为当前版本
Read-REPEATED 默认select都是一致读,避免不可重复读,也避免select加锁,增加并发性能
Read-COMMITED则不同,它的mvcc管理select会不会保证读当前事务进行前的 事务,而是最新 的事务,因此可能出现 不可重复读。
同时要注意read-commited如果 想要达到read-repeated的效果(避免不可重复读)可以使用 select * for share,在事务结束前都会尝试加锁。
主要还是
READ-REPEATED: 普通读:快照读,一致性读,加锁:加行锁还是next-key还是表锁取决于索引
READ-COMMITED: 普通读:快照读,但是读的是新版本 加锁:不会有gap锁, 对于更新语句,
还提供了半一致读:简单来说,semi-consistent read发生在update,更新的时候遇到上锁的数据会提前判断数据的prev版本,如果符合update条件才进入等待否则无需等待,减少了单行的锁冲突。也可以通过innodb_locks_unsafe_for_binlog开启。
read-uncommited和read-commit类似,唯一不同是它的读可以读到一个 更早的版本,因此被称为脏读
serialzable:序列化,更像read-repeated,但是每次查询的都不是快照读 ,而是for share加锁读 ,如果设置了自动提交,每次select都将是一个事务。
因此我认为,在设置了自动提交,默认的隔离 级别 ,无需手动调用commit
spring通过@Transaction开启事务行为,
PROPAGATION_REQUIRED 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务
PROPAGATION_SUPPORTS 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
PROPAGATION_MANDATORY 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
PROPAGATION_NOT_SUPPORTED 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
PROPAGATION_NEVER 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
PROPAGATION_NESTED 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。
其中比较难以理解的是_new和_nested,两种,简单的来说,_new,子事务和外部事务两者是完全独立的,外部事务异常不影响子事务。但是_nested如果外部事务异常,子事务也会发生回滚。
然后nested和require的区别在于如果是require的话异常发生一次性回滚了,但是nested只会回滚子事务到保存点。