mysql锁总结
myisam和innodb共有的
表锁
偏向myisam存储引擎,开销小,加锁快,无死锁,锁定粒度大,发生锁冲突的概率最高,并发量低
整张表只能被一个人使用
需要注意的是innodb也有表锁的存在,操作数据库的时候,没有使用索引去操作数据,那么行锁就会变成表锁
select * from 表名
mysql的表锁机制有俩种
查看有没有被锁过show open tables
对表进行解锁unlock tables
读锁(共享锁)
当前连接:可以查看自己不能更新,不可读取别的表
另一个连接:可以查看,当更新时候就会进入阻塞,等待解锁后才能更新
对表加读锁lock table (表名) read
写锁(独占锁)
当前连接:可以查看自己,可以更新,不能读取别的表
另一个连接:进行查询1和读取都会进入等待
对表加写锁lock table (表名) write
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预
是否兼容 | none | 读锁 | 写锁 |
---|---|---|---|
读锁(共享锁) | 是 | 是 | 否 |
写锁(独占锁) | 是 | 否 | 否 |
乐观锁与悲观锁
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
悲观锁,从字面理解就是很悲观,每次去拿数据的时候都认为别人会修改,所以在每次拿的时候对数据上锁,这样就保证了数据的准确性。比如mysql中的表锁,行锁。
乐观锁: 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
乐观锁可以通过设置一个版本号或者时间戳进行实现.
我们假设这样一种情况,业务逻辑是获取用户的账户内金钱数量小于300时+100,加完之后去修改数据库
这时俩条线程同时进行了这条逻辑
假如现在用户账户内的金钱数量是100,这时候第一条线程获取到100,并加上了100变为200,去修改这个数据的时候,第二条线程也获取到100,并加上了100变为200.去修改这行数据,我们会发现最后只增加了一次钱
如何解决这个问题呢,除了synchronized之外(锁定粒度太大)
可以在update操作的时候,在进行一次where判断,判断之前的数据有没有被修改过,如果修改过,本次更新逻辑就失败,进行一次自旋.之后再去尝试修改这个数据
mvcc和乐观锁不是一个概念!mvcc是innodb的并发版本控制,是用来以一个隐藏版本号的形式让读写可以并发执行,而乐观锁会出现的版本号,是为了防止出现aba问题.不要看到版本号就乐观锁!!!
乐观锁是要自己通过代码来进行实现的
自增锁
auto-inc自增锁是mysql一种特殊的锁,如果表中存在自增字段,mysql便会自动回复一个自增锁
他是一种特殊的表级别锁,专门针对事务插入自增类的列.
在最简单的情况下,如果一个事务正在向表中插入值,则任何事务都必须等待该表中的进行插入,以便第一个事务插入的行接收连续的主键值.
比如在插入时候另一个事务开启了自增操作抢在了前面,等这条insert语句执行完毕之后,之前开启了事务的insert语句回滚了,这样就会导致插入的时候不是一个连续的主键值了.
innodb特有的锁
行锁(记录锁)
是锁住记录,锁住所以的记录,而不是真正数据记录,顾名思义,记录所就是为某行记录加锁,它封锁该行的索引记录
- 非主键索引,会在索引加上锁后,去在主键所以上加锁
- 表上没有索引,会在主键索引上加锁
- 如果要锁的列没有索引,进行全表的记录加锁
行锁开销大,会出现思索,锁定粒度最小,发生和冲突的概率最低,并发度也最高
间隙锁(gap锁)
当使用范围查询的时候,就会给满足条件的内容加锁,比如:
update employee set name='123' where id>3 and id<7;
以上sql语句执行的时候,就会把3到7中间的内容进行加锁
锁定一个范围,不会阻塞其他的gap锁,但会阻插入间隙锁
间隙锁基于非唯一索引
(包括主键),它锁定一段范围内的索引记录
。请务必牢记:使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。
插入意向锁
插入意向锁是一种gap锁不是意向锁在insert操作的时候产生.
再多事物同时写入不同数据至同一索引时,并不需要等待其他事务完成,会发生锁等待
假设有一个记录包含键值4和7,不同的事务分别插入5-6,每个事务都会产生一个在4-7之间插入意向锁,获取在插入行上的排他(写)锁,但是不会被相互锁住,因为数据行并不冲突.
插入意向锁并不会组织任何锁,对于插入的记录会有一个行锁
临键锁(next-key)
临键锁可以理解为一种特殊的间隙锁,也可以理解为一种特殊的算法.通过临键锁可以解决幻读的问题.每个数据行上的非唯一索引都会存在一把临键锁,当某个持有该数据行的临键锁时,会锁住一段左开右闭的区间.
假设某表age字段下有:5,10,15,20
该表中 age列潜在的临键锁有:
(-∞, 5],
(5, 10],
(10, 15],
(15, 20],
(20, +∞],
在事务 A 中修改年龄为5的记录。之后如果在事务 B 中执行插入年龄为8的数据,便会被阻塞。
临键锁时记录锁与间隙所的组合,它包含的范围既包括索引记录,又包含索引区间
在句非唯一索引堆记录进行update/for update(间隙锁)/lock in share mode(共享锁),innodb会获取该记行的临键锁,并同时获取该记录下一个区间的间隙锁
临键锁主要的目的是为了避免幻读,如果把事务隔离界别设置为提交读,临键锁也会失效(只在RR也就是已提交度这个隔离界别),但innodb只在一定程度上避免了幻读,没有真正解决幻读.
那最终我们就可以得知,在根据非唯一索引
对记录行进行 UPDATE \ FOR UPDATE \ LOCK IN SHARE MODE
操作时,InnoDB 会获取该记录行的 临键锁
,并同时获取该记录行下一个区间的间隙锁
。
意向锁
表锁如何和行锁共存
举个例子事务A中锁住表中的一行,事务b锁住了一整个表
但马上就有一个问题,事务a既然锁住了一行,其他事务就不可能修改这一行.与事务b锁住了一整个表形成了冲突.所以没有意向锁的时候,行锁与表锁之间就会出现问题.
有了意向锁之后前面例子中的事务A,在申请行锁(写锁)之前,数据库会自动线给事务A申请的表加上意向拍它锁
意向所是表还是行锁
意向锁是表级别的锁,原因如下
当我们需要给一个加表锁的时候,我们需要根据意向锁判断表中有没有数据航被锁定,以确定是否能成功.如果意向锁是行锁,那么我们就得遍历表中所有数据来进行判断.如果意向锁是表锁,则我们直接判断一次就真的表中是否有数据被锁定了
IX 意向排它锁,IS 意向共享锁,X 排它锁,S 共享锁
x | IX | S | IS | |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 冲突 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |