转载请说明出处:https://blog.csdn.net/dhtx_wzgl/article/details/85230083
一、Innodb锁
1.1 锁介绍
innodb存储引擎是mysql5.1之后的默认存储引擎,相对于之前的默认存储引擎MyISAM,它有两个不同点:一是支持事物,二是采用了行级锁。下面介绍一下innodb的锁。
共享锁(S):其实就是读锁,它允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁(写锁)。当某一事物去读一行记录时,其他的事物也可以读该行,但是不能进行写操作。
排他锁(X):其实就是写锁,允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。当一个事物对某一行进行写操作时,其他事物的读写操作全部会被block,直到拿到锁为止。
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的意向共享锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的意向排他锁。
其中意向锁只能加在比行更粗的粒度上,比如库、表、页等。
锁的兼容性
1.2 锁与事物隔离的关系
mysql的事物隔离级别是可重复读,可重复读是事物级的。下面通过两个例子来看一下innodb如何通过锁来实现这一隔离级别的:
例1:假如有三个事物分别为R1、R2、W1,其中R1和R2是读事物,W1为写事物,他们都要对图中的行1进行操作,执行的顺序为R1、R2、W3。根据Innodb锁之间的兼容性可以知道这三个事物对表1和页1和行1的锁获取情况如下:
首先,R1会获得表1的IS锁、接着获得页1的IS锁,最后拿到行1的S锁;由于R2也是读操作,争取的锁也是共享的,所以会和R1拿到完全一样的锁;而W1是写操作,需要争取排他锁,在表1上已经存在的只是IS锁,所以W1可以获得它的IX锁,同样也可以获得页1的IX锁,但是在行1上已经存在S锁,所以W1只有等待R1和R2都释放掉S锁之后,才能获得行1的X锁。
结论:在先存在读的情况下,不能进行写操作,避免了读的过程中数据被改写造成不可重复读的情况。
R1 | R2 | W1 | |
---|---|---|---|
表1 | IS | IS | IX |
页1 | IS | IS | IX |
行1 | S | S |
例2:假如有三个事物分别为W1、W2、R1,其中W1和W2是写事物,R1为读事物,他们同样都要对图中的行1进行操作,执行的顺序为W1、W2、R1。根据Innodb锁之间的兼容性可以知道这三个事物对表1和页1和行1的锁获取情况如下:
首先,W1会获得表1的IX锁、接着获得页1的IX锁,最后拿到行1的X锁;由于W2也是写操作,争取的锁也是排它的,首先在争取表1的IX锁时,发现表1上只存在IX锁,所以获得锁成功,同样可以成功得到页1的IX锁,但是在争取行1的S锁时,发现行1上已经存在了一个X锁,这时只能堵塞直到该锁被释放;对于R1,需要争取共享锁,在表1上已经存在的是IX锁,所以R1可以获得它的IS锁,同样也可以获得页1的IS锁,但是在行1上已经存在X锁,所以R1也要被堵塞直到X锁被释放再去争取S锁。
结论:在先存在写的情况下,不能同时进行其他的读写操作,避免了读的过程中数据被改写造成不可重复读的情况。
W1 | W2 | R1 | |
---|---|---|---|
表1 | IX | IX | IS |
页1 | IX | IX | IS |
行1 | X |
1.3 锁升级
1、行锁只能加在索引上,如果操作不走索引,就会升级为表锁。因为innodb的行锁是加在索引上的,如果不走索引,自然就没法使用行锁了,原因是innodb是将primary key index和相关的行数据共同放在B+树的叶节点;innodb一定会有一个primary key,secondary index查找的时候,也是通过找到对应的primary,再找对应的数据行。
2、当非唯一索引上记录数超过一定数量时,行锁也会升级为表锁。测试发现当非唯一索引相同的内容不少于整个表记录的二分之一时会升级为表锁,因为当非唯一索引相同的内容达到整个记录的二分之一时,索引需要的性能比全文检索还要大,查询语句优化时会选择不走索引,造成索引失效,行锁自然就会升级为表锁。
1.4 其他
1、行排他锁与表排他锁互斥。因为表锁意味着拥有该锁的事物能够对该表中的任何记录做操作,即相当于在任意一行上加了行锁,所以会互斥。
2、如果字段在where子句中的类型与数据库类型不一致时,执行操作时索引可能会失效。原因是where子句上字段类型和数据库中字段类型不一致,mysql会自动做类型转换,即在左边加一个转换函数,所以会造成索引失效。mysq转换规则如下:
mysql隐式转换规则:
序号 | 规则 |
---|---|
1 | 两个参数至少有一个是 NULL 时,比较的结果也是 NULL ,例外是使用 <=> 对两个 NULL 做比较时会返回 1,这两种情况都不需要做类型转换 |
2 | 两个参数都是字符串,会按照字符串来比较,不做类型转换 |
3 | 两个参数都是整数,按照整数来比较,不做类型转换 |
4 | 十六进制的值和非数字做比较时,会被当做二进制串 |
5 | 有一个参数是 TIMESTAMP 或 DATETIME ,并且另外一个参数是常量,常量会被转换为 timestamp |
6 | 有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较 |
7 | 所有其他情况下,两个参数都会被转换为浮点数再进行比较 |