MySQL InnoDB下的锁问题
Posted Redick01
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL InnoDB下的锁问题相关的知识,希望对你有一定的参考价值。
文章目录
背景知识
InnoDB相比较MyISAM一是支持事务,二是支持了行级锁,提到InnoDB锁问题就不得不提到事务,所以在这之前先了解下事务的一些知识
事务及其ACID属性
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
-
原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
-
一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
-
隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
-
持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
并发事务处理带来的问题
相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多的用户。但是并发事务处理也会带来以下问题。
- 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
- 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
- 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
事务隔离级别
mysql数据库实现事务隔离的方式,基本上可分为两种。
- 一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改
- 不加锁,通过一定机制生成一个数据请求时间点的一致性数据快照,并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同意数据的多个版本,因此,这种技术叫做
数据多版本并发控制
简称MVCC。
四种事务隔离级别:
- READ UNCOMMITTED(读未提交):事务A和B操作同一数据,事务A能够读到事务B未提交的数据,会产生幻读,不可重复读,脏读
- READ COMMITTED(读已提交):事务A和B操作同一数据,事务A能够读到事务B更新的数据,会产生幻读和不可重复度
- REPEATABLE READ(可重复读):事务A和事务B操作同一数据,事务A不能读到事务B已经插入的数据,会产生幻读
- SERIALIZABLE(串行化):所有事务都必须保证串行执行,不会产生脏读,幻读,不可重复度
获取InnoDB行锁争用情况
可以通过检查InnoDB_row_lock状态变量来分析系统伤的行锁的争夺情况:
mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 0 |
+-------------------------------+-------+
5 rows in set (0.04 sec)
如果锁争用比较严重,Innodb_row_lock_waits和Innodb_row_lock_time_avg的值比较高,可以通过查询information_schema数据库中相关的表来查看锁情况,或者通过设置InnodDB Monitors来进一步观察发生锁冲突的表、数据行等,并分析其原因。
(1)通过查询information_schema数据库中的innodb_locks表了解锁的等待情况:
mysql> use information_schema;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select * from innodb_locks;
Empty set, 1 warning (0.01 sec)
mysql>
(2)通过设置InnoDB Monitors观察锁冲突情况:
mysql> create table innodb_monitor(a INT) ENGINE=INNODB;
Query OK, 0 rows affected (0.05 sec)
然后通过下面语句来进行查看:
mysql> show engine innodb status;
| Type | Name | Status
| InnoDB | |
...
------------
TRANSACTIONS
------------
Trx id counter 6076
Purge done for trx's n:o < 6071 undo n:o < 0 state: running but idle
History list length 0
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421657844005624, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 421657844004704, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 421657844006544, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
--------
FILE I/O
--------
...
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
0 read views open inside InnoDB
Process ID=1, Main thread ID=140182422546176, state: sleeping
Number of rows inserted 55250, updated 1240, deleted 376, read 22512
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================
1 row in set (0.01 sec)
监视器可以通过下列语句来停止:
mysql> drop table innodb_monitor;
Query OK, 0 rows affected (0.02 sec)
设置监视器后,在show innodb status的显示内容中,会有详细的当前锁等待的信息,包括表名、锁类型、锁定记录的情况等,便于进一步分析和定位问题。
InnoDB的行锁模式及加锁方法
InnoDB实现了两种类型的行锁
- 共享所(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
- 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁。
- 意向共享锁(IS):事务打算给数据行加共享锁,事务在给一个数据行加共享锁前必须先获得该表的IS锁。
- 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行排他锁前必须先获得该表的IX锁。
InnoDB行锁模式兼容性列表:
X | IX | S | IS | |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁收于授予该事务,否则该事务就要等待锁的释放。
意向锁是InnoDB自动加的,对于update,delete和insert语句,InnoDB是会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。
- 共享锁(S):select * from table_name where … lock in share mode;
- 排他锁(X):select * from table_name where … for update;
用lock in share mode获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行update或delete。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁。
注:session1和session2是两个连接到MySQL的客户端,使用的数据库是从mysql官网下载的,下载地址:http://downloads.mysql.com/docs/sakila-db.zip
下面是使用 lock in share mode加共享锁的例子:
session1:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from actor where actor_id=178;
+----------+------------+-----------+---------------------+
| actor_id | first_name | last_name | last_update |
+----------+------------+-----------+---------------------+
| 178 | LISA | MONROE | 2006-02-15 04:34:33 |
+----------+------------+-----------+---------------------+
1 row in set (0.00 sec)
session2:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from actor where actor_id=178;
+----------+------------+-----------+---------------------+
| actor_id | first_name | last_name | last_update |
+----------+------------+-----------+---------------------+
| 178 | LISA | MONROE | 2006-02-15 04:34:33 |
+----------+------------+-----------+---------------------+
1 row in set (0.00 sec)
session1对actor_id=178的记录加share mode的共享锁,session2也对actor_id=178加share mode的共享锁,此时session1和session2能够加共享锁,如下:
session1
mysql> select * from actor where actor_id=178 lock in share mode;
+----------+------------+-----------+---------------------+
| actor_id | first_name | last_name | last_update |
+----------+------------+-----------+---------------------+
| 178 | LISA | MONROE | 2006-02-15 04:34:33 |
+----------+------------+-----------+---------------------+
1 row in set (0.00 sec)
session2
mysql> select * from actor where actor_id=178 lock in share mode;
+----------+------------+-----------+---------------------+
| actor_id | first_name | last_name | last_update |
+----------+------------+-----------+---------------------+
| 178 | LISA | MONROE | 2006-02-15 04:34:33 |
+----------+------------+-----------+---------------------+
1 row in set (0.00 sec)
紧接着session1对178记录进行update,此时session1会等待锁,与此同时session2也对178记录更新,此时session2发生死锁,退出;
session1
mysql> update actor set last_name='monore t' where actor_id=178;
...等待
session2
mysql> mysql> update actor set last_name='monore t' where actor_id=178;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'mysql> update actor set last_name='monore t' where actor_id=178' at line 1
session2提交事务,session1获取到锁,update成功。
session2
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
session1
Query OK, 1 row affected (49.29 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from actor where actor_id=178;
+----------+------------+-----------+---------------------+
| actor_id | first_name | last_name | last_update |
+----------+------------+-----------+---------------------+
| 178 | LISA | monore t | 2021-09-02 12:47:50 |
+----------+------------+-----------+---------------------+
1 row in set (0.00 sec)
下面是使用for update加排他锁的例子:
session1对actor_id=178的行记录使用for update加排他锁,此时session2再次对178加排他锁是不会获取到锁的,会等待。
session1
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from actor where actor_id=178 for update;
+----------+------------+-----------+---------------------+
| actor_id | first_name | last_name | last_update |
+----------+------------+-----------+---------------------+
| 178 | LISA | monore t | 2021-09-02 12:47:50 |
+----------+------------+-----------+---------------------+
1 row in set (0.00 sec)
session2
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from actor where actor_id=178 for update;
...等待
session1提交事务,session2获取到锁。
session1
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
session2
mysql> select * from actor where actor_id=178 for update;
+----------+------------+-----------+---------------------+
| actor_id | first_name | last_name | last_update |
+----------+------------+-----------+---------------------+
| 178 | LISA | monore t | 2021-09-02 12:47:50 |
+----------+------------+-----------+---------------------+
1 row in set (4.84 sec)
InnoDB行锁的实现方式
InnoDB行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚集索引Row_id来对记录加锁,InnoDB行锁分为3种情形。
- Record Lock:对索引项加锁。
- Gap lock:对索引项之间的“间隙”、第一条记录前的“间隙”或最后一条记录后的“间隙”加锁。
- Next-key lock:前两种的组合,对记录及其前面的间隙加锁
InnoDB这种行锁实现特点意味着:如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟锁表一样!在实际应用中,要特别注意InnoDB行锁这一特性,否则可能导致大量的锁冲突,从而影响并发性能。
在不通过索引条件查询时,InnoDB会锁定表中的所有记录。如下,payment表的amount字段没有索引
session1加锁查询amount=8.99的数据,然后session2在加锁查询amount=3.99的数据,此时session2就会等待锁,session1 commit后session2获取到锁查询到数据。看起来session1只给amount=8.99的行加了锁,但是却出现了锁等待,原因就是在没有索引的情况下InnoDB对所有的记录加锁。
session1
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select payment_id, customer_id, staff_id, amount from payment where amount=8.99 for update;
+------------+-------------+----------+--------+
| payment_id | customer_id | staff_id | amount |
+------------+-------------+----------+--------+
| 62 | 3 | 1 | 8.99 |
| 81 | 3 | 1 | 8.99 |
| 83 | 3 | 2 | 8.99 |
...
+------------+-------------+----------+--------+
session2
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select payment_id, customer_id, staff_id, amount from payment where amount=3.99 for update;
...等待
下面我们看一下通过索引条件加锁时的情况,例如session1加锁查询payment_id=62,session2加锁查询payment_id=81;此时使用了索引,加锁就只加在符合索引条件的记录上了,并没有出现等待锁情况。
session1
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select payment_id, customer_id, staff_id, amount from payment where payment_id=62 for update;
+------------+-------------+----------+--------+
| payment_id | customer_id | staff_id | amount |
+------------+-------------+----------+--------+
| 62 | 3 | 1 | 8.99 |
+------------+-------------+----------+--------+
1 row in set (0.00 sec)
session2
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select payment_id, customer_id, staff_id, amount from payment where payment_id=81 for update;
+------------+-------------+----------+--------+
| payment_id | customer_id | staff_id | amount |
+------------+-------------+----------+--------+
| 81 | 3 | 1 | 8.99 |
+------------+-------------+----------+--------+
1 row in set (0.00 sec)
由于MySQL的行锁是对索引加的锁,所以虽然访问了不同的记录,但是如果使用相同的索引键会出现冲突的
比如payment表staff_id有索引,amount没有索引,session1加锁查询staff_id=1 and amount=8.99的记录,session2加锁查询staff_id=1 and amount=3.99的记录,session2就会等待获取锁,虽然访问的是不同的行,因为锁是加在索引上,所以会产生锁冲突。session1 commit后session2获取锁成功。
session1
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
mysql> select payment_id, customer_id, staff_id, amount from payment where staff_id=1 and amount=8.99 for update;
+------------+-------------+----------+--------+
| payment_id | customer_id | staff_id | amount |
+------------+-------------+----------+--------+
| 62 | 3 | 1 | 8.99 |
| 81 | 3 | 1 | 8.99 |
| 122 | 5 | 1 | 8.99 |
| 188 | 7 | 1 | 8.99 |
...
+------------+-------------+----------+--------+
session2
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select payment_id, customer_id, staff_id, amount from payment where staff_id=1 and amount=3.99 for update;
...等待
当表有多个索引的时候,不同事务可以使用不同的索引锁定不同的行,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁
payment表的customer_id和staff_id是索引,session1加锁查询customer_id=3,session2加锁查询staff_id=1的行,customer_id=3的数据中包含staff_id=1的数据,session2会等待锁,因为session1锁了所有customer_id=3的行,包含staff_id=1的,所以session2或等待获取锁。
session1
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select payment_id, customer_id, staff_id, amount from payment where customer_id=3 for update;
+------------+-------------+----------+--------+
| payment_id | customer_id | staff_id | amount |
+------------+-------------+----------+--------+
| 60 | 3 | 1 | 1.99 |
| 61 | 3 | 1 | 2.99 |
| 62 | 3 | 1 | 8.99 |
| 63 | 3 | 1 | 6.99 |
| 64 | 3 | 2 | 6.99 |
| 65 | 3 | 1 | 2.99 |
| 66 | 3 | 1 | 4.99 |
| 67 | 3 | 1 | 4.99 |
| 68 | 3 | 1 | 5.99 |
| 69 | 3 | 2 | 10.99 |
| 70 | 3 | 2 | 7.99 |
| 71 | 3 | 2 | 6.99 |
| 72 | 3 | 1 | 4.99 |
| 73 | 3 | 2 | 4.99 |
| 74 | 3 | 1 | 2.99 |
| 75 | 3 | 1 | 1.99 |
| 76 | 3 | 2 | 3.99 |
| 77 | 3 | 1 | 2.99 |
| 78 | 3 | 2 | 4.99 |
| 79 | 3 | 2 | 5.99 |
| 80 | 3 | 2 | 4.99 |
| 81 | 3 | 1 | 8.99 |
| 82 | 3 | 2 | 2.99 |
前言
大概几个月之前项目中用到事务,需要保证数据的强一致性,期间也用到了mysql的锁,但当时对mysql的锁机制只是管中窥豹,所以本文打算总结一下mysql的锁机制。
本文主要论述关于mysql锁机制,mysql版本为5.7,引擎为innodb,由于实际中关于innodb锁相关的知识及加锁方式很多,所以没有那么多精力罗列所有场景下的加锁过程并加以分析,仅根据现在了解的知识,结合官方文档,说说自己的理解,如果发现有不对的地方,欢迎指正。
概述
总的来说,InnoDB共有七种类型的锁:
- 共享/排它锁(Shared and Exclusive Locks)
- 意向锁(Intention Locks)
- 记录锁(Record Locks)
- 间隙锁(Gap Locks)
- 临键锁(Next-key Locks)
- 插入意向锁(Insert Intention Locks)
- 自增锁(Auto-inc Locks)
mysql锁详解
1. 共享/排它锁(Shared and Exclusive Locks)
- 共享锁(Share Locks,记为S锁),读取数据时加S锁
- 排他锁(eXclusive Locks,记为X锁),修改数据时加X锁
使用的语义为:
- 共享锁之间不互斥,简记为:读读可以并行
- 排他锁与任何锁互斥,简记为:写读,写写不可以并行
可以看到,一旦写数据的任务没有完成,数据是不能被其他任务读取的,这对并发度有较大的影响。对应到数据库,可以理解为,写事务没有提交,读相关数据的select也会被阻塞,这里的select是指加了锁的,普通的select仍然可以读到数据(快照读)。
2. 意向锁(Intention Locks)
InnoDB为了支持多粒度锁机制(multiple granularity locking),即允许行级锁与表级锁共存,而引入了意向锁(intention locks)。意向锁是指,未来的某个时刻,事务可能要加共享/排它锁了,先提前声明一个意向。
- 意向锁是一个表级别的锁(table-level locking);
- 意向锁又分为:
- 意向共享锁(intention shared lock, IS),它预示着,事务有意向对表中的某些行加共享S锁;
- 意向排它锁(intention exclusive lock, IX),它预示着,事务有意向对表中的某些行加排它X锁;
加锁的语法为:
select ... lock in share mode; 要设置IS锁;
select ... for update; 要设置IX锁;
事务要获得某些行的S/X锁,必须先获得表对应的IS/IX锁,意向锁仅仅表明意向,意向锁之间相互兼容,兼容互斥表如下:
IS
IX
IS
兼 容
兼 容
IX
兼 容
兼 容
虽然意向锁之间互相兼容,但是它与共享锁/排它锁互斥,其兼容互斥表如下:
S
X
IS
兼 容
互 斥
IX
互 斥
互 斥
排它锁是很强的锁,不与其他类型的锁兼容。这其实很好理解,修改和删除某一行的时候,必须获得强锁,禁止这一行上的其他并发,以保障数据的一致性。
3. 记录锁(Record Locks)
记录锁,它封锁索引记录,例如(其中id为pk):
create table lock_example(id smallint(10),name varchar(20),primary key id)engine=innodb;
数据库隔离级别为RR,表中有如下数据:
10, zhangsan
20, lisi
30, wangwu
select * from t where id=1 for update;
其实这里是先获取该表的意向排他锁(IX),再获取这行记录的排他锁(我的理解是因为这里直接命中索引了),以阻止其他事务插入,更新,删除id=1的这一行。
4. 间隙锁(Gap Locks)
间隙锁,它封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围。依然是上面的例子,InnoDB,RR:
select * from lock_example
where id between 8 and 15
for update;
这个SQL语句会封锁区间(8,15),以阻止其他事务插入id位于该区间的记录。
间隙锁的主要目的,就是为了防止其他事务在间隔中插入数据,以导致“不可重复读”。如果把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。
5. 临键锁(Next-key Locks)
临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。
默认情况下,innodb使用next-key locks来锁定记录。但当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。
举个例子,依然是如上的表lock_example,但是id降级为普通索引(key),也就是说即使这里声明了要加锁(for update),而且命中的是索引,但是因为索引在这里没有UK约束,所以innodb会使用next-key locks,数据库隔离级别RR:
事务A执行如下语句,未提交:
select * from lock_example where id = 20 for update;
事务B开始,执行如下语句,会阻塞:
insert into lock_example values(‘zhang‘,15);
如上的例子,事务A执行查询语句之后,默认给id=20这条记录加上了next-key lock,所以事务B插入10(包括)到30(不包括)之间的记录都会阻塞。临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。
6. 插入意向锁(Insert Intention Locks)
对已有数据行的修改与删除,必须加强互斥锁(X锁),那么对于数据的插入,是否还需要加这么强的锁,来实施互斥呢?插入意向锁,孕育而生。
插入意向锁,是间隙锁(Gap Locks)的一种(所以,也是实施在索引上的),它是专门针对insert操作的。多个事务,在同一个索引,同一个范围区间插入记录时,如果插入的位置不冲突,不会阻塞彼此。
Insert Intention Lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.
举个例子(表依然是如上的例子lock_example,数据依然是如上),事务A先执行,在10与20两条记录中插入了一行,还未提交:
insert into t values(11, xxx);
事务B后执行,也在10与20两条记录中插入了一行:
insert into t values(12, ooo);
因为是插入操作,虽然是插入同一个区间,但是插入的记录并不冲突,所以使用的是插入意向锁,此处A事务并不会阻塞B事务。
7. 自增锁(Auto-inc Locks)
自增锁是一种特殊的表级别锁(table-level lock),专门针对事务插入AUTO_INCREMENT类型的列。最简单的情况,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。
AUTO-INC lock is a special table-level lock taken by transactions inserting into tables with AUTO_INCREMENT columns. In the simplest case, if one transaction is inserting values into the table, any other transactions must wait to do their own inserts into that table, so that rows inserted by the first transaction receive consecutive primary key values.
举个例子(表依然是如上的例子lock_example),但是id为AUTO_INCREMENT,数据库表中数据为:
1, zhangsan
2, lisi
3, wangwu
事务A先执行,还未提交: insert into t(name) values(xxx);
事务B后执行: insert into t(name) values(ooo);
此时事务B插入操作会阻塞,直到事务A提交。
总结
以上总结的7种锁,个人理解可以按两种方式来区分:
1. 按锁的互斥程度来划分,可以分为共享、排他锁;
- 共享锁(S锁、IS锁),可以提高读读并发;
- 为了保证数据强一致,InnoDB使用强互斥锁(X锁、IX锁),保证同一行记录修改与删除的串行性;
2. 按锁的粒度来划分,可以分为:
- 表锁:意向锁(IS锁、IX锁)、自增锁;
- 行锁:记录锁、间隙锁、临键锁、插入意向锁;
其中
- InnoDB的细粒度锁(即行锁),是实现在索引记录上的(我的理解是如果未命中索引则会失效);
- 记录锁锁定索引记录;间隙锁锁定间隔,防止间隔中被其他事务插入;临键锁锁定索引记录+间隔,防止幻读;
- InnoDB使用插入意向锁,可以提高插入并发;
- 间隙锁(gap lock)与临键锁(next-key lock)只在RR以上的级别生效,RC下会失效;