MySQL设计这个锁是干啥用的
数据库锁设计的初衷是处理并发问题。作为多用户共享的资源,当出现并发访问的时候,为了保证数据的一致性,数据库需要合理地控制资源的访问,制定了一些访问规则。而锁就是用来实现这些访问规则的重要机制。
通过锁实现了在并发情况下数据的一致性
锁的分类
按锁粒度从大到小分类:表锁,页锁和行锁;以及特殊场景下使用的全局锁
如果按锁级别分类则有:共享(读)锁、排他(写)锁、意向共享(读)锁、意向排他(写)锁;
以及Innodb引擎为解决幻读等并发场景下事务存在的数据问题,引入的Record Lock(行记录锁)、Gap Lock(间隙锁)、Next-key Lock(Record Lock + Gap Lock结合)等;
还有就是我们面向编程的两种锁思想:悲观锁、乐观锁
锁粒度
表锁
特点:MySQL各存储引擎中最大颗粒度的锁定机制
优点:
- 实现逻辑非常简单,带来的系统负面影响最小
- 获取锁和释放锁的速度很快
- 有效避免死锁的发生
缺点:出现锁定资源争用的概率也会最高,大大降低并发度
适用场景:表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用
行锁
特点:锁定对象的颗粒度很小
优点:发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力从而提高系统的整体性能
缺点:
- 由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大
- 行级锁定也最容易发生死锁
使用场景:有大量按索引条件并发更新数据的情况,同时又有并发查询的应用场景。
InnoDB存储引擎中锁的类型
共享锁(S锁)
允许读一行数据
多个事务可以共享一条记录的读锁(锁兼容)
但是如果有别的事务再想要获得该条记录写锁就不行(锁不兼容)
排他锁 (X锁)
允许更新和删除一行数据
与X,S锁不兼容
以上两种锁都是行锁
意向锁
InnoDB存储引擎支持多粒度(granular)锁定,这种锁定允许事务在行级上的
锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,InnoDB存储引擎支持
一种额外的锁方式,称之为意向锁(Intention Lock)。
意向锁意味着事务希望在更细粒度(fine granularity)上进行加锁
意向锁属于表级锁,其设计目的主要是为了在一个事务中揭示下一行将要被请求锁的类型
加意向锁表示,该表中的某一行,要被加上读锁或者写锁
若将上锁的对象看成一棵树,那么对最下层的对象上锁,也就是对最细粒度的对象
进行上锁,那么首先需要对粗粒度的对象上锁。
例如,如果需要对页上的记录r进行上X锁,那么分别需要对数据库A、表、页上意向锁X,最后对记录r上X锁。
InnoDB 中的两个表锁:
-
意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁;
-
意向排他锁(IX):类似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。
意向锁是 InnoDB 自动加的,不需要用户干预。
对于INSERT、UPDATE和DELETE,InnoDB 会自动给涉及的数据加排他锁;对于一般的SELECT语句,InnoDB 不会加任何锁,事务可以通过以下语句显式加共享锁或排他锁。
共享锁:SELECT … LOCK IN SHARE MODE;
排他锁:SELECT … FOR UPDATE;
举例:
看一个例子感受一下意向锁的好处
比如说一个一千条数据的表,事务1和事务2对其操作
事务1读取一行记录
事务2修改整个表
在没有意向锁的情况下:
事务1获取了这个表的一行R的共享锁
事务2想要修改整个表中的所有数据,要求给这表加上写锁
事务2要做的事情是:
- 看看表是否被加表锁了
- 没有加表锁,还要看看表中的每一行记录是否被上行锁
而再有意向锁的情况下:
3. 事务1在给行加共享锁时,就给表加上了IS锁
4. 事务2再去加排他锁时,首先看看表是否被加表锁了,如果有锁,就会发生阻塞。只有等事务1释放IS锁后才能加X锁,而不用重复那么多一条条去查找是否有行被加锁。
IS锁 不兼容 X锁
死锁
死锁是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种相互等待的现象。若无外力作用,事务都将无法推进下去。
在并发系统中,不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程进入无限等待的状态,成为死锁。
死锁解决手段:
- 超时机制
超时的事务回滚掉,从而释放资源
- 等待图机制
采用等待图(wait-for graph)的方式来进行死锁检测
wait-for graph要求数据库保存以下两种信息:
锁的信息链表
事务等待链表
通过上述链表可以构造出一张图,而在这个图中若存在回路,就代表存在死锁,因此资源间相互发生等待。
来看一个例子:
在 Transaction Wait Lists中可以看到共有4个事务t1、t2、t3、t4,故在wait-for graph中应有4个节点。
通过上图可以发现存在回路(t1,t2),因此存在死锁。可以发现wait-for graph是一种较为主动的死锁检测机制,在每个事务请求锁并发生等待时都会判断是否存在回路,若存在则有死锁,通常来说InnoDB存储引擎选择回滚undo量最小的事务。
锁操作
手动添加锁
lock table 表名字1 read (write),表名字2 read(write), 其他…
分析表锁定
show status like ‘table%’;
查看加锁情况
show open tables
手动解锁
unlock tables;
问题:
- 什么时候会发生死锁
- 为什么表级锁能有效避免死锁的发生
- 行级锁定也最容易发生死锁 为什么
测试读锁共享锁
1.建表 (MyISAM)
2.锁表read
3.session1访问
4.session2访问
5.加了读锁是否可以修改?session1 cannot write
6.session1是否可读别的表 cannot
7、session2 write 对于session1加了读锁的表会发生阻塞、知道session1释放锁,session2操作完成
session1 对表A加写锁
1.session1是否可读 yes
2.session1是否可写 yes
3.session1是否可以查看其它表 no
4.session2是否可以读表A ----阻塞
5.session2是否可以写表A ----阻塞
总结:读操作会阻塞写,但不会阻塞读,而写操作会把读和写都阻塞
MyISAM偏读