本文仅涉及的InnoDB存储引擎
.MySQL不同的存储引擎支持不同的锁机制。
一,Spring Transaction的事务传播行为
1.PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务。
2.PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
3.PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
4.PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
5.PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6.PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
7.PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
二,事务的隔离级别
READ_UNCOMMITTED这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
READ_COMMITTED保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
REPEATABLE_READ这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读。
SERIALIZABLE这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读.
InnoDB 默认事务隔离级别为可重复读(重复读取,RR)
三,事务的三级封锁协议
1.一级封锁协议:事务Ť中如果对数据ř有写操作,必须在这个事务中对 - [R的第一次读操作前对它加X锁,直到事务结束才释放事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。
2.二级封锁协议:一级封锁协议加上事务Ť在读取数据ř之前必须先对其加小号锁,读完后方可释放小号锁。
3.三级封锁协议:一级封锁协议加上事务Ť在读取数据ř之前必须先对其加小号锁,直到事务结束才释放。
满足高级锁则一定满足低级锁。但有个非常致命的地方,一级锁协议就要在第一次读加X锁,直到事务结束。几乎就要在整个事务加写锁了,效率非常低。
三级封锁协议只是一个理论上的东西,实际数据库常用另一套方法来解决事务并发问题。
四,锁粒度分类
按照锁的粒度划分时,MySQL的有3种锁:1行锁2表锁3页面锁。
五,InnoDB的中的7种锁
1.共享/排他锁
粒度:行锁
InnoDB的行的锁的英文的索引通过上的索引项来实现的,只有通过索引条件检索数据时,InnoDB的的才会使用行级锁,否则时,InnoDB的的将使用表锁。
不同的数据行,只要访问的是同一个索引(不同的数据行,但索引值相同)也会被锁。
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。
-
事务拿到某一行记录的共享S锁,才可以读取这一行;
-
事务拿到某一行记录的排它X锁,才可以修改或者删除这一行;
-
多个事务可以拿到一把S锁,读读可以并行;
-
而只有一个事务可以拿到X锁,写写,读写串行;
目的:
1.共享锁:提高读读并发
2.排他锁:保证数据的一致性
2.意向锁(Intention Locks)
粒度:表锁
意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁S前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁X前必须先取得该表的IX锁。
意向锁仅仅表明意向,它是比较弱的锁,意向锁之间并不相互互斥,而是可以并行的。
兼容
兼容 | X | IX | S | IS |
X(行) | 否 | 否 | 否 | 否 |
IX | 否 | 是 | 否 | 是 |
S(行) | 否 | 否 | 是 | 是 |
IS | 否 | 是 | 是 | 是 |
目的:
1.IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突
2.意向锁是在添加行锁之前添加
0.3。如果没有意向锁,当向一个表添加表级X锁时,就需要遍历整张表来判断是否存行锁,以免发生冲突
4.如果有了意向锁,只需要判断该意向锁与表级锁是否兼容即可。
3.插入意向锁(插入意图锁)
粒度:行锁
插入意向锁,是间隙锁(Gap Locks)的一种(所以,也是实施在索引上的),它是专门针对插入操作的。
多个事务,在同一个索引,同一个范围区间插入记录时,如果插入的位置不冲突,不会阻塞彼此。
目的:提高写写并发
ID | name |
1 | S |
8 | a |
16 | C |
事务1开启 | 事务2开启 |
insert t value(2,'Q') | insert t value(5,'H') |
select * from t; | select * from t; |
1 s 2 q 8 a 16 c |
1 s 5 h 8 a 16 c |
事务1提交 | 事务2提交 |
事物一和事物二都是对表的同一索引范围进行插入,使用的插入意向锁,由于插入的记录并不冲突,所以并不会阻塞事物二。
如果事物二插入的记录与事物一冲突,会被X锁阻塞。
4.间隙锁(Gap Locks)
粒度:行锁
封锁记录中的间隔,防止间隔中被其他事务插入。
间隙锁基于非唯一索引,它锁定一段范围内(开区间)的索引记录。
锁住的是一个区间,不仅仅是而这个区间中的每一条数据。
SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;
即在所有(1,10)
区间内的记录行都会被锁住,所有ID为2,3,4,5,6,7,8,9的数据行的插入会被阻塞,但是1和10两条记录行并不会被锁住。
例如学生表中只有5条记录,ID的值分别是1,2,3,4,5
SELECT * FROM student WHERE id > 4 FOR UPDATE
该SQL是一个范围条件的检索时,InnoDB的不仅会对符合条件的ID值为5的记录加锁,也会对ID大于5(这些记录并不存在)的“间隙”加锁。
当用范围条件而不是相等条件检索数据时,InnoDB的会给符合条件的已有数据的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”时, InnoDB中也会对这个“间隙”加锁。
在使用范围条件检索并锁定记录时,InnoDB的的这种加锁机制会阻塞符合条件范围内键值的并发插入,会造成严重的锁等待。因此,在实际开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
如果把事务的隔离级别降级为读提交(Read Committed,RC),间隙锁则会自动失效。
5.临键锁(Next-Key Locks)
粒度:行锁
一种特殊的间隙锁,解决幻读。
每个数据行上的非唯一索引列
上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开闭向左向右区间的数据。需要强调的一点是,InnoDB
中行级锁
的英文基于索引实现的,在唯一索引列
(包括主键列
)上不存在临键锁。
如果把事务的隔离级别降级为RC,临键锁会失效。
6.自增锁(Auto-inc Locks)
粒度:表锁
专门针对事务插入AUTO_INCREMENT类型的列
如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。
show variables like 'innodb_autoinc_lock_mode';
mysql> show variables like 'innodb_autoinc_lock_mode';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| innodb_autoinc_lock_mode | 1 |
+--------------------------+-------+
innodb_autoinc_lock_mode有3种配置模式:0、1、2,分别对应”传统模式”, “连续模式”, “交错模式”。
传统模式:涉及auto-increment列的插入语句加的表级AUTO-INC锁,只有插入执行结束后才会释放锁。这是一种兼容MySQL 5.1之前版本的策略。
连续模式:可以事先确定插入行数的语句(包括单行和多行插入),分配连续的确定的auto-increment值;对于插入行数不确定的插入语句,仍加表锁。这种模式下,事务回滚,auto-increment值不会回滚,换句话说,自增列内容会不连续。
交错模式:同一时刻多条SQL语句产生交错的auto-increment值。
7.记录锁(记录锁)
粒度:行锁
记录锁存在于包括主键索引
在内的唯一索引
中,锁定单条索引记录。
例如:select * from t where id = 1 for update;
需要注意的是:id
列必须为唯一索引列
或主键列
,否则上述语句加的锁就会变成临键锁
。
它会在ID = 1的索引记录上加锁,以阻止其他事务插入,更新,删除ID = 1的这一行。
说明:select * from t,其中id = 1; 则是快照读(SnapShot Read),它并不加锁
六,加锁方式
1.自动
表锁:
SQL语句不命中索引,则InnoDB的使用表锁。
意向锁是InnoDB的自动加的,不需显示声明。
行锁:
对于更新,删除和INSERT语句时,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句时,InnoDB不会加任何锁。
InnoDB的行锁是通过索引上的索引项来实现的,这一点的MySQL与甲骨文不同,后者是通过在数据中对相应。只有通过索引条件检索数据,InnoDB的才会使用行级锁,否则,InnoDB的将使用表锁。
不同的数据行,只要访问的是同一个索引(不同的数据行,但索引值相同)也会被锁。
2.显示声明
表锁:
1.使用LOCK TALBES虽然可以给InnoDB加表级锁,但表锁不是由InnoDB存储引擎层管理的,而是由其上一层MySQL Server负责的,仅当autocommit = 0,innodb_table_lock = 1(默认设置)时,InnoDB的层才能知道的MySQL加的表锁时,MySQL服务器才能感知的InnoDB加的行锁,这种情况下,InnoDB的才能自动识别涉及表级锁的死锁;否则,InnoDB的将无法自动检测并处理这种死锁。
2.在用LOCK TABLES对InnoDB锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁 ;事务结束前,不要用UNLOCAK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务; COMMIT或ROLLBACK时不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES显示释放表锁。
SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES
行锁:
共享锁(S):
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X):
SELECT * FROM table_name WHERE ... FOR UPDATE
如果对同一行记录先进行查询再进行修改,为了防止当前事务出现死锁,应该直接加排他锁。
七,查询行锁竞争情况
show status like 'innodb_row_lock%';
innodb_row_lock_current_waits当前正在等待锁的数量
innodb_row_lock_time从系统启动到现在,锁定总时长
innodb_row_lock_time_avg每次等待所花平均时间
innodb_row_lock_time_max从系统启动到现在,最长的一次等待所花时间
innodb_row_lock_waits从系统启动到现在,总共等待次数