Mysql共享锁、排他锁
名词及含义
- 表级锁(锁定整个表)
- 页级锁(锁定一页)
- 行级锁(锁定一行)
- 共享锁(S锁,MyISAM 叫做读锁)
- 排他锁(X锁,MyISAM 叫做写锁)
- 间隙锁(NEXT-KEY锁)
- 悲观锁(抽象性,不真实存在这个锁)
- 乐观锁(抽象性,不真实存在这个锁)
InnoDB与MyISAM
我之前专程写了Mysql中MyISAM和InnoDB区别和Mysql存储引擎
这里主要写一些影响锁相关的内容
Mysql 在5.5之前默认使用 MyISAM 存储引擎,之后使用 InnoDB 。查看当前存储引擎:
show variables like '%storage_engine%';
MyISAM 操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高。当然同时它也不会存在死锁问题。
而 InnoDB 与 MyISAM 的最大不同有两点:一是 InnoDB 支持事务;二是 InnoDB 采用了行级锁。也就是你需要修改哪行,就可以只锁定哪行。
在 Mysql 中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql 语句操作了主键索引,Mysql 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。
共享锁与排他锁
数据库的增删改操作默认都会加排他锁,而查询不会加任何锁。
mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。
共享锁
对某一资源加共享锁,自身可以读该资源,其他人也可以读该资源(也可以再继续加共享锁,即 共享锁可多个共存),但无法修改。要想修改就必须等所有共享锁都释放完之后。语法为:
select * from table lock in share mode
排他锁
对某一资源加排他锁,自身可以进行增删改查,其他人无法进行任何操作。语法为:
select * from table for update
排他锁和共享锁 只会对加锁的sql语句进行阻塞
例如A事务:
start TRANSACTION;
select * from table_name where id = 1 for UPDATE;
B事务
# 不会阻塞 直接查出答案
select * from table_name where id = 1
C事务
# 阻塞 等待事务A提交
select * from table_name where id = 1 for UPDATE;
间隙锁
什么叫间隙锁
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(NEXT-KEY)锁。
间隙锁的产生
上面的文字很抽象,现在举个栗子,介绍间隙锁是怎么产生的:
假设有以下表t_student:
id | name | sex | address |
---|---|---|---|
1 | a | 0 | cq |
3 | b | 1 | cq |
4 | c | 0 | cq |
5 | d | 1 | cq |
6 | e | 0 | cq |
这个时候我们发出一条这样的加锁sql语句:
start TRANSACTION;
select id,name from t_student where id > 0 and id < 5 for update;
这时候,我们命中的数据为以下加粗部分:
id | name | sex | address |
---|---|---|---|
1 | a | 0 | cq |
3 | b | 1 | cq |
4 | c | 0 | cq |
5 | d | 1 | cq |
6 | e | 0 | cq |
细心的朋友可能就会发现,这里缺少了条id为2的记录,我们的重点就在这里。
select ... for update
这条语句,是会对数据记录加锁的,这里因为命中了索引,加的是行锁。从数据记录来看,这里排它锁锁住数据是id为1、3和4的这3条数据。
但是,看看前面我们的介绍——对于键值在条件范围内但不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁。
好了,我们这里,键值在条件范围但是不存在的记录,就是id为2的记录,这里会对id为2数据加上间隙锁。假设这时候如果有id=2的记录insert进来了,是要等到这个事务结束以后才会执行的
间隙锁的作用
总的来说,有2个作用:防止幻读和防止数据误删/改
因为加锁了,所以其他(删/改/增)都会被阻塞
使用间隙锁的隐患
最大的隐患就是性能问题
如果间隙锁出现死锁的情况下,会更隐晦,更难定位