首页 > 其他分享 >select...for update 到底是加了行锁,还是表锁?

select...for update 到底是加了行锁,还是表锁?

时间:2024-09-09 22:26:37浏览次数:7  
标签:... 事务 行锁 age update user where id

原文:select...for update 到底是加了行锁,还是表锁?

前言

select...for update 在 MySQL 中,是一种悲观锁的用法,一般情况下,会锁住一行数据,但如果没有使用正确的话,也会把整张表锁住。

1. 要什么要用行锁?

假如现在有这样一种业务场景:用户 A 给你转账了 2000 元,用户 B 给你转账了 3000 元,而你的账户初始化金额是 1000 元。

在事务 1 中会执行下面这条 SQL:

update account set money=money+2000 
where id=123;

在事务 2 中执行下面这条 SQL:

update account set money=money+3000 
where id=123;

这两条 SQL 执行成功之后,你的 money 可能是:3000、4000、6000,这三种情况中的一种。

你之前的想法是,用户 A 和用户 B 总共给你转账 5000,最终你账户的钱应该是 6000 才对,3000 和 4000 是怎么来的?

假如事务 1 在执行 update 语句的过程中,事务 2 同时也在执行 update 语句。

事务 1 中查询到 money 是 1000,此外事务 2 也查询到 money 是 1000。

如果事务 1 先执行 update 语句,事务 2 后执行 update 语句,第一次 update 的 3000,会被后面的 4000 覆盖掉,最终结果为 4000。

如果事务 2 先执行 update 语句,事务 1 后执行 update 语句,第一次 update 的 4000,会被后面的 3000 覆盖掉,最终结果为 3000。

这两种情况都产生了严重的数据问题。

我们需要有某种机制,保证事务 1 和事务 2 要顺序执行,不要一起执行。

这就需要加锁了。

目前 MySQL 中使用比较多的有:表锁、行锁和间隙锁。

我们这个业务场景,非常时候使用行锁

在事务 1 执行 update 语句的过程中,先要把某一行数据锁住,此时,其他的事务必须等待事务 1 执行完,提交了事务,才能获取那一行的数据。

在 MySQL 中是通过 select...for update 语句来实现的行锁的功能。

但如果你在实际工作中使用不正确,也容易把整张表锁住,严重影响性能。

select...where...for update 语句的用法是否正确,跟 where 条件中的参数有很大的关系。

我们一起看看下面几种情况。


假如 user 表现在有这样的数据库,数据库的版本是:8.0.21。

img

创建的索引如下:

img

其中 id 是主键字段,code 是唯一索引字段,name 是普通索引字段,其他的都是普通字段。

2. 主键

当 where 条件用的数据库主键时。

例如开启一个事务 1,在事务中更新 id=1 的用户的年龄:

begin;
select * from user where id=1 for update;
update user set age=22 where id=1;

where 条件中的 id 是数据库的主键,并且使用 for update 关键字,加了一个行锁,这个事务没有 commit。

此时,开启了另外一个事务 2,也更新 id=1 的用户的年龄:

begin;
update user set age=23 where id=1;
commit;

在执行事务 2 的 SQL 语句的过程中,会一直等待事务 1 释放锁:

img

如果事务 1 一直都不释放行锁,事务 2 最后会报下面这个异常:

img

如果此时开始一个事务 3,更新 id=2 的用户的年龄:

begin;
update user set age=23 where id=2;
commit;

执行结果如下:

img

由于事务 3 中更新的另外一行数据,因此可以执行成功。

说明使用 for update 关键字,锁住了主键 id=1 的那一行数据,对其他行的数据并没有影响。

3. 唯一索引

当 where 条件用的数据库唯一索引时。

开启一个事务 1,在事务中更新 code=101 的用户的年龄:

begin;
select * from user where code='101' for update;
update user set age=22 where code='101';

where 条件中的 code 是数据库的唯一索引,并且使用 for update 关键字,加了一个行锁,这个事务没有 commit。

此时,开启了另外一个事务 2,也更新 code=101 的用户的年龄:

begin;
update user set age=23 where code='101';
commit;

执行结果跟主键的情况是一样的。

img

4. 普通索引

当 where 条件用的数据库普通索引时。

开启一个事务 1,在事务中更新 name=周星驰的用户的年龄:

begin;
select * from user where name='周星驰' for update;
update user set age=22 where name='周星驰';

where 条件中的 name 是数据库的普通索引,并且使用 for update 关键字,加了一个行锁,这个事务没有 commit。

此时,开启了另外一个事务 2,也更新 name=周星驰的用户的年龄:

begin;
update user set age=23 where name='周星驰';
commit;

执行结果跟主键的情况也是一样的。

img

另:如果有多个 name 为周星驰的行,则这些行都会被锁住。

5. 主键范围

当 where 条件用的数据库主键范围时。

开启一个事务 1,在事务中更新 id in (1,2) 的用户的年龄:

begin;
select * from user where id in (1,2) for update;
update user set age=22 where id in (1,2);

where 条件中的 id 是数据库的主键范围,并且使用 for update 关键字,加了多个行锁,这个事务没有 commit。

此时,开启了另外一个事务 2,也更新 id=1 的用户的年龄:

begin;
update user set age=23 where id=1;
commit;

执行结果跟主键的情况也是一样的。

img

此时,开启了另外一个事务 2,也更新 id=2 的用户的年龄:

begin;
update user set age=23 where id=2;
commit;

执行结果跟主键的情况也是一样的。

img

6. 普通字段

当 where 条件用的数据库普通字段时。

该字段既不是主键,也不是索引。

开启一个事务 1,在事务中更新 age=22 的用户的年龄:

begin;
select * from user where age=22 for update;
update user set age=22 where age=22 ;

where 条件中的 age 是数据库的普通字段,并且使用 for update 关键字,加的是表锁,这个事务没有 commit。

此时,开启了另外一个事务 2,也更新 age=22 的用户的年龄:

begin;
update user set age=23 where age=22 ;
commit;

此时,执行事务 2 时,会一直阻塞等待事务 1 释放锁。

调整一下 SQL 条件,查询条件改成 age=23:

begin;
update user set age=23 where age=23 ;
commit;

此时,行事务 3 时,也会一直阻塞等待事务 1 释放锁。

也就是说,在 for update 语句中,使用普通字段作为查询条件时,加的是表锁,而并非行锁。

7. 空数据

当 where 条件查询的数据不存在时,会发生什么呢?

开启一个事务 1,在事务中更新 id=66 的用户的年龄:

begin;
select * from user where id=66 for update;
update user set age=22 where id=66 ;

这条数据是不存在的。

此时,开启了另外一个事务 2,也更新 id=66 的用户的年龄:

begin;
update user set age=23 where id=66 ;
commit;

执行结果:

img

执行成功了,说明这种情况没有加锁。

总结

最后给大家总结一下 select...for update 加锁的情况:

  1. 主键字段:加行锁。
  2. 唯一索引字段:加行锁。
  3. 普通索引字段:加行锁。
  4. 主键范围:加多个行锁。
  5. 普通字段:加表锁。
  6. 查询空数据:不加锁。

如果事务 1 加了行锁,一直没有释放锁,事务 2 操作相同行的数据时,会一直等待直到超时。

如果事务 1 加了表锁,一直没有释放锁,事务 2 不管操作的是哪一行数据,都会一直等待直到超时。

扩展

现在考虑一个问题:在 MySQL 中,事务 A 中使用 select...for update where id=1 锁住了,某一条数据,事务还没提交,此时,事务 B 中去用 select ... where id=1 查询那条数据,会阻塞等待吗?

标签:...,事务,行锁,age,update,user,where,id
From: https://www.cnblogs.com/Higurashi-kagome/p/18405461

相关文章

  • 【倒计时10天...IEEE出版】第五届大数据、人工智能与软件工程国际研讨会(ICBASE 2024,9
    第五届大数据、人工智能与软件工程国际研讨会(ICBASE2024)将于2024年09月20-22日在中国温州隆重举行。会议主要围绕大数据、人工智能与软件工程等研究领域展开讨论。会议旨在为从事大数据、人工智能与软件工程研究的专家学者、工程技术人员、技术研发人员提供一个共享科研......
  • MySQL数据库insert,delete,update,select语句
    MySQL数据库insert,delete,update,select语句是在做数据处理是的重要部分首先我们要先拥有一个表格,这里我建立了一个student表然后在这个表格中添加一些数据这里要使用insert的语句INSERTINTOstudent(NO,NAME,age)VALUES(1001,'张三',20);这里我创建到的表有三个属性......
  • Eclipse Check For Updates总是检查不到更新-解决方法
    最近想用流行的JavaIDE——Eclipse的Help->CheckForUpdates更新Eclipse,结果总是说未找到更新:1.检查是否有更新首先,访问Eclipse下载地址获得最新版本(目前为2020-06):再看看系统中的Eclipse版本(Help->AboutEclipseIDE):如果网上版本和当前版本相等(像图中),那就System.......
  • 使用微信小程序-云开发时报错: Error: errCode: -401003 api parameter type error |
    错误Uncaught(inpromise)thirdScriptErrorerrCode:-401003apiparametertypeerror|errMsg:parameter.datashouldbeobjectinsteadofundefined;Error:errCode:-401003apiparametertypeerror|errMsg:parameter.datashouldbeobjectinsteadofundef......
  • 《三》行锁-表锁-间隙锁
    《三》行锁-表锁-间隙锁在RR级别下,forupdate,lockinsharemode默认加的锁时next-key锁一、行锁当select语句走的是主键索引时,next-key变更为行锁二、表锁当select语句没有走索引时,为表锁三、间隙锁当select语句走的是普通索引,包括唯一索引时为间隙锁CREATETAB......
  • 《三》行锁-表锁-间隙锁
    《三》行锁-表锁-间隙锁在RR级别下,forupdate,lockinsharemode默认加的锁时next-key锁一、行锁当select语句走的是主键索引时,next-key变更为行锁二、表锁当select语句没有走索引时,为表锁三、间隙锁当select语句走的是普通索引,包括唯一索引时为间隙锁CREATETABL......
  • INSERT ... ON DUPLICATE KEY UPDATE 问题记录
    起因:需要新增复制数据并更新原数据状态,故采用INSERT...ONDUPLICATEKEYUPDATE的方式来插入和更新数据问题:数据插入及更新异常环境:MySQL5.7.32数据表结构:点击查看代码CREATETABLE`example_table`(`col_a`varchar(255)NOTNULL,`col_b`varchar(255)NOTN......
  • pbootcms提示:“未检测到您服务器环境的sqlite3数据库扩展...”
    解决方法主要有两种,需根据具体情况进行选择。 第一种方法是将数据库配置连接驱动改为pdo_sqlite。首先,按照相关提示,把数据库配置连接驱动修改为pdo_sqlite。随后,可依照图文进行操作。具体而言,打开数据库配置文件,即位于/config/database.php的文件。在该文件中,找到'type'这......
  • 2.3 在同一个图形界面中分别画出6条曲线 y=kx^2+2k k=1,2,3...
    点击查看代码importnumpyasnpimportmatplotlib.pyplotasplt#定义x的范围x=np.linspace(-10,10,400)#创建一个图形和坐标轴plt.figure(figsize=(10,6))ax=plt.gca()#循环绘制每条曲线colors=['r','g','b','c......
  • 解决Mac安装软件的“已损坏,无法打开...”问题
    解决Mac安装软件的“已损坏,无法打开。您应该将它移到废纸篓”问题不管在安装时会遇到以已损坏无法打开的困惑,解决:一、允许“任何来源”开启苹果从macOSSierra10.12开始,已经去除了允许“任何来源”的选项,如果不开启“任何来源”的选项,会直接影响到无法运行的第三方应用。......