MySQL中select...for update锁表

 更新时间:2023年10月25日 11:45:43   作者:Elivis Hu  
select…for update在MySQL中,是一种悲观锁的用法,一般情况下,会锁住一行数据,但如果没有使用正确的话,也会把整张表锁住,本文就来介绍一下,感兴趣的可以了解一下

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

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

其实,我之前也在实际项目中试过用,比如:积分兑换礼品的功能。

今天跟大家一起聊聊select…for update这个话题,希望对你会有所帮助。

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,数据库的隔离级别是:REPEATABLE-READ。

图片

创建的索引如下:

图片

其中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释放锁。

图片

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

图片

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

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

执行结果如下:

图片

由于事务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;

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

图片

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;

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

图片

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;

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

图片

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

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

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

图片

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;

执行结果:

图片

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

总结

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

  • 主键字段:加行锁。
  • 唯一索引字段:加行锁。
  • 普通索引字段:加行锁。
  • 主键范围:加多个行锁。
  • 普通字段:加表锁。
  • 查询空数据:不加锁。

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

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

到此这篇关于MySQL中select...for update锁表的文章就介绍到这了,更多相关select...for update锁表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL查询数据库中某个库、表、索引等所占空间的大小

    MySQL查询数据库中某个库、表、索引等所占空间的大小

    MySQL是一个非常流行的关系型数据库管理系统,它的查询操作是非常强大和灵活的。查询的效率不仅取决于表的大小,而且还取决于索引的大小,本文主要介绍了MySQL查询数据库中某个库、表、索引等所占空间的大小,感兴趣的可以了解一下
    2024-01-01
  • 阿里面试MySQL死锁问题的处理

    阿里面试MySQL死锁问题的处理

    这篇文章主要介绍了在阿里面试中的一个问题MySQL死锁问题的处理回答,对常见的死锁案例进行相关分析与探讨,以及如何去尽可能避免死锁给出一些建议
    2022-03-03
  • mysql8.0如何修改root密码

    mysql8.0如何修改root密码

    这篇文章主要介绍了mysql8.0修改root密码的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05
  • Windows 10 与 MySQL 5.5 安装使用及免安装使用详细教程(图文)

    Windows 10 与 MySQL 5.5 安装使用及免安装使用详细教程(图文)

    本文介绍Windows 10环境下,MySQL 5.5的安装使用及免安装使用教程,本文提供了资源下载及相关问题解决方案,非常不错,需要的朋友参考下
    2017-07-07
  • MySQL 数据丢失排查案例

    MySQL 数据丢失排查案例

    这篇文章主要分享了MySQL 数据丢失排查的一个案例,帮助大家更好的理解和学习使用MySQL数据库,感兴趣的朋友可以了解下
    2021-05-05
  • 浅谈MySQL大表优化方案

    浅谈MySQL大表优化方案

    这篇文章主要介绍了浅谈MySQL大表优化方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • Mysql 中的多表连接和连接类型详解

    Mysql 中的多表连接和连接类型详解

    这篇文章详细介绍了MySQL中的多表连接及其各种类型,包括内连接、左连接、右连接、全外连接、自连接和交叉连接,通过这些连接方式,可以将分散在不同表中的相关数据组合在一起,从而进行更复杂的查询和分析,感兴趣的朋友一起看看吧
    2025-01-01
  • MySQL 导入慢的解决方法

    MySQL 导入慢的解决方法

    MySQL导出的SQL语句在导入时有可能会非常非常慢,在导出时合理使用几个参数,可以大大加快导 入的速度。
    2010-12-12
  • 一文详解MySQL中避免隐式转换的最佳实践

    一文详解MySQL中避免隐式转换的最佳实践

    在MySQL数据库开发中,隐式类型转换是一个常见但容易被忽视的问题,本文将深入探讨MySQL中的隐式转换机制,分析其带来的问题,并提供实用的解决方案来帮助开发者避免这些陷阱,感兴趣的小伙伴可以了解下
    2026-02-02
  • Centos 7.9安装MySQL8.0.32的详细教程

    Centos 7.9安装MySQL8.0.32的详细教程

    这篇文章主要介绍了Centos7.9安装MySQL8.0.32的详细教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03

最新评论