Skip to content

Latest commit

 

History

History
155 lines (73 loc) · 6.74 KB

MySQLLock.md

File metadata and controls

155 lines (73 loc) · 6.74 KB

MySQL 锁总结

Lock

不仅是资源占有的一种处理机制,更是多线程或并发编程下对数据一致性的一种保证。加锁和释放锁本身也会消耗资源。了解并合理利用锁机制,能大大提升数据库的性能。

锁的作用者是事务,也就是说,锁是针对事务使用而言。单个操作不显式的开启和提交/回滚事务,默认情况下每个操作会自动的开启一个事务。


共享锁(S):

一个事务对数据加共享锁,也可以允许其他事务对此交集数据加此锁。但阻止其他事务对此交集数据加排他锁。

加共享锁语句:

SELECT * FROM table_name WHERE ...
LOCK IN SHARE MODE

排他锁(X):

一个事务对数据加排他锁,会阻止其他事务对此交集数据加任何锁。

加排他锁锁语句:

SELECT * FROM table_name WHERE ...
FOR UPDATE

意向锁:

为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),在这里的两种意向锁都是表锁(绕脑)。

意向共享锁(IS):

事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):

事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

锁的冲突与兼容:

锁的冲突与兼容 X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

**备注:**意向锁是 InnoDB 自动加的, 不需用户干预。


表级锁:

每个事务操作会锁住整张表,粒子度最大,简单粗暴。优点是加锁和释放锁次数会大大减少。缺点是锁冲突的概率会大大增加,高并发情况下不可取。

页级锁:

资源开销介于行级锁和表级锁,会出现死锁。

行级锁:

每个事务仅会锁住被影响的行,也就是说,涉及到哪些行记录,哪些行才会被锁住,会出现死锁。优点是锁冲突概率小,并发度高。缺点是由于锁离子度小,加锁和释放锁的次数会大大增加,资源开销大。

**切记:**MySQL的行级锁通过索引上的索引项来实现的,InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁。


间隙锁(Next-Key锁)

当我们用范围条件而不是相等条件检索数据,并请求共享锁或排他锁时,InnoDB会给符合条件的已有数据的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做 “间隙(GAP)” ,InnoDB也会对这个“间隙”加锁,这种锁机制不是所谓的 间隙锁(Next-Key锁)

举例来说,假如employee表中只有101条记录,其employee_id的值分别是1,2,...,100,101,下面的SQL:

SELECT id, name, age, department
FROM employee
WHERE employee_id > 100
FOR UPDATE

是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。

InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了employee_id大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另一方面,是为了满足其恢复和复制的需要。有关其恢复和复制对机制的影响,以及不同隔离级别下InnoDB使用间隙锁的情况。

很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。


死锁:

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环。当事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也可能会产生死锁。

InnoDB避免死锁:

  • 为了在单个InnoDB表上执行多个并发写入操作时避免死锁,可以在事务开始时通过为预期要修改的每个记录(行)使用SELECT ... FOR UPDATE语句来获取必要的锁,即使这些行的更改语句是在之后才执行的。
  • 在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁、更新时再申请排他锁,因为这时候当用户再申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁
  • 如果事务需要修改或锁定多个表,则应在每个事务中以相同的顺序使用加锁语句。 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会
  • 通过SELECT ... LOCK IN SHARE MODE获取行的读锁后,如果当前事务再需要对该记录进行更新操作,则很有可能造成死锁。
  • 改变事务隔离级别

如果出现死锁,可以用 SHOW INNODB STATUS 命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的 SQL 语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。


乐观锁:

加锁是为了占用资源,我们上面说过加锁和释放锁都会有资源开销。在有些不需要加锁就能获取资源岂不是更好?。乐观锁是乐观的认为在抢占资源时不用加锁就能获取资源(因为没有其他事务抢占资源或者发生的冲突概率小,稍稍尝试几次就能成功,美滋滋)。适用于冲突概率小的情景下。可以参考之前 关于CAS的文章

悲观锁:

在冲突概率大的情况下,悲观的认为抢不到资源或者多次都抢不到资源。只能通过加锁的方式的抢占资源,然后再做处理,最后释放资源。


参考资料:

MySQL锁总结