共享锁:又称读锁,是读取数据创建的锁。多个事务对同一数据可以共享一把锁,只能进行读取不能进行任何修改操作,直到已经释放所有共享锁。共享锁可以叠加(事务A对数据D加上共享锁后,则其他事务只能对数据D再加共享锁,不能加排他锁)。就相当于你家房间那个门有好几把钥匙,你一把、你女朋友一把,你们都可以进去啪啪啪什么的,别人想来打扰(修改)也不行。
SELECT ... LOCK IN SHARE MODE;
在查询语句后面增加
LOCK IN SHARE MODE
,Mysql会对查询结果中的每行都加共享锁
排它锁:又称写锁。如果事务A对数据D加上排他锁后,则其他事务不能再对数据D加任何类型的锁(即排它锁不能与其他所共存),只有获取排他锁的事务A是可以对数据D就行读取和修改。其它事务也不能对D做update,insert,delete操作,因为在innodb中这些操作默认加了排他锁,可以进行select 操作因为查询的时候是不加任何锁的。
SELECT ... FOR UPDATE;
在查询语句后面增加
FOR UPDATE
,Mysql会对查询结果中的每行都加排他锁,当没有其他事务对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。
下面用例子来说明:
eg1:这里对id=1的数据加排它锁,用begin开启事务,注意并没有commit和rollback关闭事务释放锁:
会查询出一条数据,现在分别对该数据进行排它锁和共享锁查询操作:
我们看到开了排他锁查询和共享锁查询都会处于阻塞状态,因为id=1的数据已经被加上了排他锁,此处阻塞是等待排他锁释放。
但是我们可以用普通的select语句查询(因为没用上锁机制不与排他锁互斥):
eg2:我们在对id=2的数据,添加共享锁,在其他查询中也只能加共享锁或不加锁:
进行排他查询,阻塞
进行共享查询和普通查询,成功
乐观锁与悲观锁
悲观锁:是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。当程序独占锁时,其他程序就连查询都是不允许的,导致吞吐较低。如果在查询较多的情况下,可使用乐观锁。
乐观锁:是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入较频繁,对吞吐要求不高,可使用悲观锁。
也就是一句话:读用乐观锁,写用悲观锁。
行锁与死锁
MyISAM中是不会产生死锁的,因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待。而在InnoDB中,锁是逐步获得的,就造成了死锁的可能。
在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。 在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。
当两个事务同时执行,一个锁住了主键索引在等待其他相关索引,一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。