InnoDB锁

InnoDB是一个多线程并发的存储引擎,内部的读写都是使用多线程来实现的,所以InnoDB内部实现了一个比较高效的并发同步机制。InnoDB并没有直接使用系统提供的锁同步结构(latch),而是对其进行封装实现优化,同时兼容系统的锁。

测试数据

DROP TABLE IF EXISTS account;
CREATE TABLE IF NOT EXISTS account(
  id INT(11) unsigned NOT NULL AUTO_INCREMENT,
  code INT(11) NOT NULL DEFAULT 0,
  PRIMARY KEY(id),
  KEY(code)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO account(id, code) VALUES(10,10),(20,20),(30,30),(40,40),(50,50),(60,60),(70,70)

注意:id为主键索引,code为普通索引(非唯一键二级索引)不要使用唯一索引。

查看锁状态

查看锁状态必须手动开启多个事务,某些锁的状态的查看必须在锁处于waiting状态才能在MySQL引擎日志中查看到。

命令:显示最近几个事务的状态、查询、写入情况等信息,当出现死锁时会给出最近死锁的明细。

mysql> SHOW ENGINE innodb STATUS;
------------
TRANSACTIONS
------------
Trx id counter 12BA05
Purge done for trx's n:o < 12BA03 undo n:o < 0
History list length 944
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0, not started
MySQL thread id 8, OS thread handle 0x51d8, query id 75 localhost 127.0.0.1 root
SHOW ENGINE innodb STATUS
---TRANSACTION 12BA04, not started
MySQL thread id 7, OS thread handle 0x575c, query id 72 localhost 127.0.0.1 root
---TRANSACTION 0, not started
MySQL thread id 5, OS thread handle 0x6980, query id 65 localhost 127.0.0.1 root

S锁和X锁

InnoDB存储引擎实现了两种标准行级锁,一种是S锁(Shared Locks,S锁,乐观锁,共享锁),一种是X锁(Exclusive Locks, X锁,互斥锁,排它锁,独占锁)。S锁允许当前持有该锁的事务读取行,X锁允许当前持有该锁的事务更新或删除行。

行锁 名称 描述
S锁 乐观锁,共享锁 允许当前持有S锁的事务读取行
X锁 互斥锁,排它锁,独占锁 允许当前持有X锁的事务更新或删除行

如果某事务持有某行上的S锁,则其他事务可以同时持有该行的S锁,但不能对该行添加X锁。如果某事务持有某行上的X锁,则其他事务不能持有该行的X锁,必须等待该行的X锁释放。

如果某事务在某行上保持S锁,则另一个事务对该行上锁请求的处理方式可分为两种情况:另一事务可以同时持有S锁,另一事务若想在该行上获取X锁必须等待其他事务对该行添加的S锁或X锁的释放。

意向锁(Intention Locks)

InnoDB存储引擎支持多种粒度的锁,允许行级锁和表级锁的共存。

例如:为指定的表添加X锁

LOCK TABLES tblname WRITE

InnoDB使用意向锁(Intention Locks)实现多个粒度级别的锁定。意向锁(Intention Lock)是表级锁,表示表中的行锁需要的锁的类型。意向锁可以分为意向共享锁(IS锁)和意向排它锁(IX锁), IS锁表示当前事务意图在表中的行上设置S锁。IX锁表示当前事务意图在表中的行上设置X锁。

意向锁 名称 描述
IS锁 意向共享锁 当前事务意图在表中的行上设置S锁
IX锁 意向排它锁 当前事务意图在表中的行上设置X锁

例如:获取IS锁

SELECT ... LOCK IN SHARE MODE;

例如:获取IX锁

SELECT ... FOR UPDATE;

注意,事务要获取某个表上的S锁和X锁之前,必须先分别获取对应的IS锁和IX锁。

InnoDB锁的兼容矩阵

如果不同事务之间的锁兼容则当前加锁事务可以持有锁,如果有冲突则会等待其他事务的锁释放。如果一个事务请求锁时,请求的锁与已经持有的锁冲突而无法获取时,相互等待就可能会产生死锁。意向锁不会阻止除了全表锁定请求之外的任何锁请求。意向锁的主要目的是显示事务正在锁定某行或正意图锁定某行。

兼容性 X锁 IX锁 S锁 IS锁
X锁 N N N N
IX锁 N Y N Y
S锁 N N Y Y
IS锁 N Y Y Y

记录锁(Record Lock)

  • 记录锁是对索引记录的锁定,记录锁分为两种模式S模式和X模式。
  • 记录锁始终只锁定索引,即使表没有建立索引,InnoDB也会创建一个隐藏的聚簇索引(隐藏的递增主键索引),并使用此索引进行记录锁定。

例如:防止任何其他事务插入、更新、删除id为10的行

SELECT id FROM account WHERE id=10 FOR UPDATE;

查看记录所

mysql> SHOW ENGINE innodb STATUS;

事务1:开启事务更新数据后不提交

mysql> START TRANSACTION;
mysql> UPDATE account SET id=100 WHERE id=10;

查看记录锁

mysql> SHOW ENGINE innodb STATUS;
------------
TRANSACTIONS
------------
Trx id counter 12BA07
Purge done for trx's n:o < 12BA03 undo n:o < 0
History list length 944
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 12BA04, not started
MySQL thread id 7, OS thread handle 0x575c, query id 72 localhost 127.0.0.1 root
---TRANSACTION 0, not started
MySQL thread id 5, OS thread handle 0x6980, query id 65 localhost 127.0.0.1 root
---TRANSACTION 12BA06, ACTIVE 44 sec
3 lock struct(s), heap size 320, 2 row lock(s), undo log entries 2
MySQL thread id 8, OS thread handle 0x51d8, query id 87 localhost 127.0.0.1 root
SHOW ENGINE innodb STATUS

UPDATE语句会为id为1的行添加X锁

---TRANSACTION 12BA06, ACTIVE 44 sec
3 lock struct(s), heap size 320, 2 row lock(s), undo log entries 2
MySQL thread id 8, OS thread handle 0x51d8, query id 87 localhost 127.0.0.1 root
SHOW ENGINE innodb STATUS

注意:X锁广义上是一种抽象意义的排它锁,即锁一般分为X模式和S模式。狭义上是指行或索引上的锁。记录锁是索引上的锁,为了不修改数据,使用SELECT ... FOR UPDATE语句加锁行为和UPDATEDELETE是一样的。INSERT加锁机制则较为复杂。

事务2:事务1保持原状不提交或回滚,此时开启第二个事务。

mysql> START TRANSACTION;
Query OK, 0 rows affected

mysql> UPATE account SET id=200 WHERE id=10;
1205 - Lock wait timeout exceeded; try restarting transaction

事务2执行UPDATE时SQL语句的执行是被阻塞的

此时查看存储引擎状态

mysql> SHOW ENGINE innodb STATUS;
------------
TRANSACTIONS
------------
Trx id counter 12BA0D
Purge done for trx's n:o < 12BA08 undo n:o < 0
History list length 945
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 12BA0A, not started
MySQL thread id 10, OS thread handle 0x575c, query id 113 localhost 127.0.0.1 root
---TRANSACTION 0, not started
MySQL thread id 5, OS thread handle 0x6980, query id 107 localhost 127.0.0.1 root
---TRANSACTION 12BA0C, ACTIVE 22 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 320, 1 row lock(s)
MySQL thread id 8, OS thread handle 0x51d8, query id 120 localhost 127.0.0.1 root Updating
UPDATE lock_test SET id=200 WHERE id=1
------- TRX HAS BEEN WAITING 22 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 2768 n bits 80 index `PRIMARY` of table `test`.`lock_test` trx id 12BA0C lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 32
 0: len 4; hex 00000001; asc     ;;
 1: len 6; hex 00000012ba0b; asc       ;;
 2: len 7; hex 0a0000013c1e12; asc     <  ;;
 3: len 4; hex 80000001; asc     ;;

间隙锁(Gap Locks)

间隙锁是用来解决幻读的一大手段,通过这种手段,可以没有必要将事务隔离级别调整到序列化这个最严格的级别,而导致并发量大大下降。

间隙锁作用在索引记录之间的间隙,或作用在第一个索引之前最后一个索引之后的间隙,不包括索引本身。
间隙锁的间隙可以跨越零个、单个或多个索引值。间隙锁是性能和并发权衡的产物,只存在于部分事务隔离级别。

例如:唯一索引可以锁定一行,所以无需间隙锁锁定。如果列没有索引或具有非唯一索引,则该语句会锁定当前索引前的间隙。

SELECT * FROM account WHERE id=10;

例如:阻止其他事务插入code为1到10之间的数字,无论这个数字是否存在。

SELECT code FROM account WHERE code BETWEEN 1 AND 10 FOR UPDATE;

在同一个间隙上不同事务可以持有兼容或冲突表中冲突的两个锁,比如事务1现在持有一个间隙S锁,事务2可同时在同一个间隙上持有间隙X锁。允许冲突的锁在间隙上锁定的原因是,若从索引中清除一条记录则由不同事务在这条索引记录上的加间隙锁的动作必须被合并。

InnoDB中的间隙锁的唯一目的是防止其他事务插入间隙,间隙锁是可以共存的,一个事务占用的间隙锁不会阻止另一个事务获取同一个间隙上的间隙锁。如果事务隔离级别修改为RC则间隙锁会被禁用。

查看间隙锁

按官方文档WHERE子句查询条件是唯一键且指定了值时只有记录锁没有间隙锁,如果WHERE语句制定了范围间隙锁是存在的。当指定非唯一键索引时间隙锁的位置会锁定当前索引以及索引之前的间隙。制定了非唯一键索引,间隙锁仍然会存在。

例如:

开启事务1,锁定一条非唯一的普通索引记录。

mysql> START TRANSACTION;

锁定code=70的索引,同时锁定code<70的间隙。

mysql> SELECT * FROM account WHERE code=70 FOR UPDATE;

开启事务2,在code=70之前的间隙中插入一条数据。

mysql> START TRANSACTION;

插入时执行被阻塞

mysql> INSERT INTO account(69, 69);

此时查看引擎状态

mysql> SHOW ENGINE innodb STATUS;

Next-key 锁

Next-key锁实际上是Record锁和Gap锁的组合,Next-key锁是在下一个索引记录本身和索引之前的间隙添加S锁或X锁。默认情况下,InnoDB存储引擎的事务隔离级别为RR,系统参数innodb_locks_unsafe_for_binlog的值为false。InnoDB使用Next-key锁对索引进行扫描和搜索,这样就读取不到幻象行,避免了幻读的发生。幻读是指在同一事务下,连续执行两次同样的SQL语句,第二次的SQL语句可能会返回之前不存在的行。当查询的索引是唯一索引时,Next-key锁会进行优化,降级为Record锁,此时Next-key锁仅仅作用在索引本身,而不会作用于间隙和下一个索引上。

自增锁(AUTO-INC Locks)

自增锁是事务插入时自增列上特殊的表级别的锁,最简单的情况是如果一个事务正在向表中插入值,则任何其他事务必须等待,以便第一个事务插入的行接收连续的主键值。一般会将主键设置为AUTO_INCREMENT的列,默认情况下此字段的值为0,InnoDB会在AUTO_INCREMENT修饰下的数据列所关联的索引末尾设置独占锁。在访问自增计数器时,InnoDB使用自增锁,但锁定仅持续到当前SQL语句的末尾,而非整个事务的结束,毕竟自增锁是表级别的锁,若长期锁定会大大降低数据库的性能。由于是表锁,使用期间其他会话是无法插入表中。


InnoDB锁优化

  • 尽可能让所有数据检索都通过索引来完成,从而避免InnoDB因为无法通过索引键加锁而升级为表级锁定。
  • 合理涉及索引,让InnoDB在索引键上加锁时尽可能准确,尽可能缩小锁定范围,避免造成不必要的锁定而影响其它Query的执行。
  • 尽量控制事务的大小以减少锁定的资源量和锁定时间长度
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355