前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于MySQL锁的两个知识点

关于MySQL锁的两个知识点

作者头像
AsiaYe
发布2019-11-06 16:16:52
4280
发布2019-11-06 16:16:52
举报
文章被收录于专栏:DBA随笔DBA随笔
关于MySQL中锁的两个知识点
1

MySQL快照读和当前读

当我们对数据库中的表进行select、update、delete以及insert的时候,innodb存储引擎会根据操作类型的不同来给这些操作添加具体的锁。

在MySQL中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。

这里我们首先给出快照读和当前读的例子:

快照读:简单的select操作,属于快照读,不加锁。(当然,也有例外,下面会分析)

代码语言:javascript
复制
select * from table where id>10;

当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。

代码语言:javascript
复制
select * from table where id>10 lock in share mode;
select * from table where id>10 for update;
insert into table values (…);
update table set id=11 where id=10;
delete from table where id>10;

读取之后,需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句明确指出了lock in share mode之外,也就是对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。

这里我们给出一个update操作过程中,mysql server和innodb存储引擎进行交互的过程如下:

从上图中,我们可以看出一个update操作的具体流程。当update SQL被发给MySQL后,MySQL Server会根据where条件,读取第一条满足条件的记录,然后InnoDB引擎会将第一条记录返回,并加锁 (current read)。待MySQL Server收到这条加锁的记录之后,会再发起一个update请求,更新这条记录。一条记录操作完成,再读取下一条记录,直至没有满足条件的记录为止。因此,update操作内部,就包含了一个当前读。同理,delete操作也一样。insert操作会稍微有些不同,简单来说,就是insert操作可能会触发Unique Key的冲突检查,也会进行一个当前读。

2

关于死锁

死锁是指两个或者两个以上的事务在执行的过程中,因争夺资源而造成的一种互相等待的现象。若无外力作用,这两个事务将保持等待状态,无法推进下去。很明显,这是我们不想看到的。

从上面的概念可以看出,死锁的关键点在于互相等待,如果我们要解决死锁的问题,就要从“等待”这个关键词上面入手,如果我们将等待都转化为回滚操作,并且事务都重新开始,这种方法无疑可以避免死锁问题的产生。但是会导致数据库并发性能的降低,这样的问题也是我们无法接受的。

为了解决这一问题,我们采用一种超时的方法进行折中进行处理,超时是指当两个事务互相等待时,当某一方的等待时间超过一个阈值,我们将它进行回滚,这样,另一个事务就能够继续进行,在innodb存储引擎中,我们使用参数innodb_lock_wait_timeout来设置超时时间,这个参数如下:

代码语言:javascript
复制
mysql> show variables like "innodb_lock_wait_timeout";
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set, 1 warning (0.11 sec)

为了加深印象,我们模拟一个死锁的现象,让大家感受一下。

首先,要模拟死锁,程序必须并发运行,串行的方法是无法模拟死锁的,这里我们采用两个连接会话进行模拟:

会话A

我们先开启事务,然后锁定id=3的行;

代码语言:javascript
复制
mysql> select * from t;
+----+-----+
| id | age |
+----+-----+
|  1 |   5 |
|  2 |   4 |
|  3 |   3 |
|  4 |   2 |
|  5 |   1 |
+----+-----+
5 rows in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where id=3 for update;
+----+-----+
| id | age |
+----+-----+
|  3 |   3 |
+----+-----+
1 row in set (0.02 sec)

会话B

在会话B上锁定id=2的行

代码语言:javascript
复制
mysql> begin
    -> ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t where id=2 for update;
+----+-----+
| id | age |
+----+-----+
|  2 |   4 |
+----+-----+
1 row in set (0.00 sec)

会话A

我们在会话A上获取id=2的记录的锁,发现无法获取,产生了等待:

代码语言:javascript
复制
mysql> select * from t where id=2 for update;
##产生等待

mysql>

会话B

在会话A进行等待的过程中,我们在会话B上面获取id=3的记录的锁,我们发现了两个变化:

第一、会话B上输出了死锁的提示信息,如下;

代码语言:javascript
复制
mysql> select * from t where id=3 for update;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting traction

mysql>

第二、会话A上输出了id=2的记录,也就是A会话得到了特定的资源,但是产生了9s的延迟,如下;

会话A

代码语言:javascript
复制
mysql> select * from t where id=2 for update;
+----+-----+
| id | age |
+----+-----+
|  2 |   4 |
+----+-----+
1 row in set (9.04 sec)

mysql>

在上述操作中,会话B抛出了1213这个错误状态码,它代表事务发生了死锁,死锁的原因是会话A和B的资源进行了相互等待,但是此时我们发现会话B中抛出死锁提示信息之后会话A中立即得到了记录为2的这个资源,这其实是因为会话B中的事务发生了回滚,否则的话,会话A中的事务是不可能得到相应的资源的。

这里又不得不提innodb的一个特性,那就是它会回滚死锁情况下的一个事务,因此当我们在程序中捕获了一个1213的错误,其实不需要我们手动进行回滚。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DBA随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档