mysql锁机制
锁是数据库针对并发问题提出的解决方案
我的理解:加锁的本质是对要操作的数据进行限制,防止他人操作这部分数据对自己产生影响,操作包含读和写,若不想他人操作这部分数据,直接加排他锁,若是想允许他人操作这部分数据则加共享锁;而自己对数据也有读写两种操作,只写就加共享锁,读和写就加排他锁
根据锁住的行数分为:
表级锁:锁住整张表的数据,innodb和myisam都有
页级锁:bdb数据库中才有,锁住一页的数据
行级锁:锁住一行的数据,innodb才有
并发性从上到下越来越好,上锁开销从上到下越来越大,加锁速度越来越慢,锁冲突概率越来越低
重点是innodb下的行级锁
(分类)
共享锁(s):也称为读锁,任何事务都只能读,不能修改,也即其他事务只能加读锁,不能加排他锁,多个共享锁可以共存
排他锁(x):也称为写锁,允许本事务读写,阻止其他事务读写(也就是独享锁),多个排他锁互斥,会出现锁等待现象
排他锁和(排他锁和共享锁)互斥,排他锁可以防止超卖,使业务强制串行化
加锁方式:加锁,必须开启事务
共享锁,select ....... lock in share mode;
排他锁,select ....... for update;
在innodb中,对update,insert,alter等写操作,mysql会自动加上排他锁(也就是会自动开启事务并加排他锁),普通select操作不会加任何锁
乐观锁和悲观锁,是一种主观分类,不是一种锁机制
悲观锁:不管加什么锁,只要是上了锁都属于悲观锁,因为加了锁之后,其他事务就不能进行写操作
乐观锁:乐观锁并不是真的加锁,是通过技术手段达到加锁的效果;mvcc 多版本并发控制是乐观锁的最佳实践,如:
主键 用户名 年龄。 数据版本号
Id username age version
1 chenjuan. 18 1
$res = Select * from user where id = 1
update user set username =“cj”,version = version+1 where id =1 and version=$res[‘version’]
每次更新前都要查询一次获取到版本号,更新时将版本号作为条件进行更新,所以,如果其他的进程修改了数据,则version会改变,那么我们的更新语句就会失败
所以防止超卖有两个方案:(都是防止其他事务修改数据)
用悲观锁的方式:加排他锁,防止其他事务修改
用乐观锁的方式:加版本号,以更改前的版本号作为修改条件,同时更新版本号和数据
死锁
死锁是一种现象,是多个事务间相互等待的过程,一般范围查询和模糊查询容易出现死锁
比如事务1对id=1的数据加了排他锁,事务2对id=2的数据加了排他锁,然后事务1要修改=2的数据,由于事务2事务没有结束,事务1就会进入锁等待;然后事务2要修改id=1的数据,由于事务1在等待,没有结束事务,所以事务2也进入锁等待,这就是两个事务之间相互等待
死锁的处理方式:
(1)不会相互等待,一个事务直接报错,一个事务会执行成功;
(2)通过配置文件的配置项innodb_lock_time_out 设置锁等待时间,锁等待时间结束后一个成功一个失败;
哪个成功哪个失败取决于事务的大小,一般是update>select
间隙锁
使用的查询不是等式查询而是范围查询,并加共享锁或者排他锁
现象:锁住的范围会是这个范围内的所有数据,包括不存在的数据,比如说查询 id > 1 and id < 10的数据,这条sql会查询到id=2,3,4,5,6,7,8,9,即使id=6的数据不存在,这样就导致另一个事物新增不了id=6的数据
加间隙锁锁住了不存在的数据,解决了并发事务中的幻读问题
行锁升级为表锁:(包括排他锁和共享锁)使用主键索引和唯一索引不会出现此问题
现象:看起来是对个别行加锁,实际是对整个表加锁,因为此时其他行无法加任何锁,应尽量避免出现表锁,因为表锁表明并发行很低
出现的原因是因为锁定的列不是索引列就会升级为表锁
以下情况会出现行锁升级为表锁:
1)以没加索引的字段作为查询条件(等值查询或者范围查询),加锁
2)以加了普通索引的字段作为查询条件,并且范围查找,导致索引失效,加锁