MySQL原理(三)锁定机制(4)常见的行锁行为&影响哪些操作&对DB的影响

一、并发串行锁表

背景

我们的业务是禁用@Transactional的。但是有个古老的service接口使用了事务注解,我在第一句加了一个简单的updateByUserId语句更新一个user级别的表,结果产线监控发现这个小表的update sql执行了18s,那么为什么一句简单的sql会超时呢?

1、其他耗时步骤?

分析代码,这个service接口并没有什么比较耗时的操作,相对耗时的除了update db,还有一个http call,难道是http call超时了?

  • SQL 执行本身不会慢

  • 事务内耗时操作(HTTP / sleep / 计算)会延长锁持有时间

  • 其他事务访问同一行就可能 SQL 超时

2、并发量大?

日志显示,这次的http call本身也不耗时只有0.0几秒。我们根据超时的这次userId+业务关键字进一步搜索日志发现同一时刻这个事务service接口调用量有100次,也就是说100个事务同时抢占写锁,最后一个事务拿到写锁时一定是其他的99个事务执行完了,所以最后一个事务的执行时长是100个事务的总时长。从日志也能看出来,这个sql耗时一个比一个长。

1、问题抛出

@Transactional

public void service() {

updateDb(); // SQL

... // 省略逻辑(可能是 sleep、HTTP、其他逻辑)

}

1.1、"SQL 执行成功"到底指什么

这里有个误区:updateDb() 执行完了 → 数据已经写入数据库,但不完全正确:

  • 数据确实被数据库记录在 undo/redo log 里

  • 但外部事务看不到,锁也仍然占着

  • 如果事务回滚,数据会消失

1.2、过程

updateDb() 执行时:

  • SQL 已经发送到数据库

  • 数据库已经执行了修改(UPDATE 已经生效在 当前事务里

  • 事务还没有提交

所以:

  • 从数据库内部来看,这条 SQL 已经"执行成功"

  • 但是对外部事务(其他事务)不可见,对其他会话/连接的查询、更新仍然被锁阻塞

2、事务对sql的影响

2.1、省略号里的逻辑是否影响 SQL 成功?
省略号逻辑类型 对 updateDb() 的执行影响
sleep / HTTP call / 计算 不影响 SQL 的执行,SQL 已在数据库执行成功(事务内部)
抛异常(RuntimeException) 会触发 事务回滚 → SQL 的修改最终 不会生效
修改其他表 / SQL 不影响已经执行的 updateDb() 本身,但会延长锁持有时间
2.2、省略号里的逻辑是否影响 SQL 耗时?
复制代码
@Transactional
public void service() {
    updateDb();          // 10ms
    Thread.sleep(7s);    // 7s
    callRemoteApi();     // 3s
}

sql本身的执行时间是不受影响的,是否超时要看db锁的情况,如果事务A在执行,事务B同时需要访问db的同一行,我们来看下A、B的耗时:

✅ 关键结论:

  1. 事务 A 自己的耗时是整个方法耗时(10 s),不会超时

  2. 事务 B 因为锁阻塞可能超时,取决于数据库 lock wait 设置

  3. SQL 执行时间本身都是短的(10 ms),超时来自 锁等待

(1)事务A

T0 BEGIN TRANSACTION

T0~T0+10ms updateDb() 执行完成(事务内部成功)

T0+10ms~T10s Thread.sleep(7s) + callRemoteApi(3s)

T10s COMMIT

  • SQL 执行耗时:10 ms

  • 事务总耗时(锁持有时间):约 10 s

✅ 事务 A 的sql本身不会超时,除非数据库对单事务有超长执行限制(很少见)。

(2)事务B

事务 B 同时尝试更新同一行:

T0 BEGIN TRANSACTION B

T0~T10s 等待事务 A 释放锁

T10s+ updateDb() 执行 10ms

T10s+ COMMIT

  • 阻塞时间:约 10 s(等待锁)

  • SQL 执行时间:10 ms

  • 事务总耗时:10 s + 10 ms ≈ 10 s

事务B的sql是否超时取决于 数据库锁等待超时时间 :如果你的数据库锁等待超时时间 小于 10 秒 ,事务 B 就会 SQLTimeoutException;否则不会。

数据库 默认锁等待超时 事务 B 阻塞是否超时?
MySQL InnoDB 50 s (innodb_lock_wait_timeout) 不会超时(10 s < 50 s)
PostgreSQL 无限等待行锁(可设置 lock_timeout 不会超时,除非设置了 lock_timeout
Oracle 无限等待(可用 NOWAITWAIT n 不会超时,除非设置了等待限制

3、sql超时的原因------并发和锁

回到最开始的问题,为什么会出现 SQL 超时,这就是经典的 "事务阻塞/锁导致 SQL 超时" 问题,事务里还有耗时操作 导致锁持有时间变长,影响并发 SQL。而不是 SQL 本身慢。核心原因:锁阻塞。

(1)事务延长

  • updateDb() 执行成功(事务内部)

  • 但事务未提交 → 行锁/表锁仍在数据库里被持有

(2)其他 SQL 同步访问同一行

  • 如果另一个事务/线程同时执行同一行的 update 或 select ... for update

  • 它就会 等待锁释放

  • 如果等待超过数据库的 lock wait 超时时间(MySQL 默认 50s,PostgreSQL 也是类似) → SQL 报 超时

(3)HTTP 调用影响

  • HTTP 调用耗时几秒甚至几十秒

  • 事务一直没提交

  • 其他事务就一直等待锁 → 超时

4、典型 SQL 超时示意

(1)事务 A

T0 updateDb() 执行成功

T0~T5 httpCall() 等待响应

T5 commit

(2)事务 B(同时更新同一行)

T0 update 同一行 → 等锁

T5+ 事务超时 → 报 SQLTimeoutException

5、分析监控

sql等待锁

SQL 等待锁 = SQL 已经到数据库了,但因为别人占着它需要的锁,只能停在那里等,对方提交或回滚。

事务 A:

@Transactional public void service() { update t set status = 1 where id = 1; // 拿到 id=1 的行锁 sleep / http call // 一直不 commit }

事务 B(几乎同时):

update t set status = 2 where id = 1;

1️⃣ A 先执行

  • A 执行 update id=1

  • 数据库做了两件事:

    • 修改数据(事务内)

    • 给 id=1 这行加了行锁

锁的意思是:

"这行现在我在改,别人不能动。"


2️⃣ B 执行 update(这一步就是"等待锁")

  • B 的 SQL 已经发到数据库

  • 数据库发现:

    • id=1 的行锁 被 A 持有
  • 数据库不会报错,也不会直接执行


那什么时候不等了?

只有 3 种结果:

✅ 1. A 提交(commit)

  • 锁释放

  • B 立刻执行 update(几 ms)

  • SQL 成功

❌ 2. A 回滚(rollback)

  • 锁释放

  • B 执行 update

  • SQL 成功

💥 3. 等太久 → 超时

  • 等待时间 > 数据库配置的锁等待超时

  • B 报错:

MySQL

Lock wait timeout exceeded; try restarting transaction

Java

SQLTimeoutException

那么这个在监控里面会算作执行时长吗

要看"是哪一层的监控"。

监控位置 等锁算不算执行时长
应用层(JDBC / APM) ✅ 算
MyBatis / Hibernate 日志 ✅ 算
慢 SQL 日志 ❌ 通常不算
DB 锁等待监控 单独统计
(1)应用层 / JDBC / APM(最常见)

如Spring 日志、JDBC Driver、SkyWalking / Pinpoint / NewRelic、MyBatis / Hibernate SQL 监控,中间发生了什么它不关心,它们的计时方式是:

复制代码
start = 调用 executeUpdate()
end   = executeUpdate() 返回
  • SQL 在等锁 10 秒

  • executeUpdate() 卡 10 秒

  • 监控里看到:SQL 执行 10 秒

**(2)**数据库慢 SQL 日志(MySQL / PG / Oracle)

MySQL slow log

  • 记录的是:

    • SQL 真正执行(CPU / IO)
  • 锁等待通常不算在内(或单独统计)

    Query_time: 0.01
    Lock_time: 9.99

(3)数据库锁监控 / 事务视图(最准)

information_schema.innodb_trx

performance_schema.data_lock_waits

6、去掉事务能变快吗?

如果并发更新的是db的同一行,那么即使去掉事务也不能彻底解决锁表的问题,顶多是减少了一点锁表的时间(减少了http call的总时长)。那么如何解决呢?

(1)方案 1

如果业务允许的话:

复制代码
synchronized (LOCK) {
    updateById();
}

或:

  • 单线程消费

  • MQ 串行 consumer

(2)拆热点

把 1 行拆成 N 行:

复制代码
id = 1_0
id = 1_1
id = 1_2
...
  • 按 hash / 线程 / 时间分桶

  • 最终再汇总

👉 数据库才能并行

(3)用 Redis / 内存抗并发

复制代码
请求 → Redis incr → 异步落库

数据库只承受低频写。类似于电商平台的秒杀这种高并发业务:

用户请求

网关限流

内存缓存(Redis)原子扣减库存

排队消息队列(MQ)

后台异步落库(写订单、减库存)

(4)CAS / 乐观锁(适合可失败场景)

复制代码
UPDATE table
SET value = value + 1, version = version + 1
WHERE id = ? AND version = ?

二、(行锁)锁表会阻塞该表的哪些操作

如锁住的是id=1这一行,那么同时发生:

操作 是否阻塞
SELECT count FROM A WHERE id = 1 ❌ 不阻塞
SELECT ... FOR UPDATE ✅ 阻塞
UPDATE A SET ... WHERE id = 1 ✅ 阻塞
UPDATE A SET ... WHERE id = 2 ❌(不同主键)

1、普通 SELECT(不阻塞)

复制代码
SELECT * FROM A WHERE id = 1;

2、加锁读(阻塞)

复制代码
SELECT * FROM A WHERE id = 1 FOR UPDATE;

或者

复制代码
SELECT * FROM A WHERE id = 1 LOCK IN SHARE MODE;

3、UPDATE / DELETE(阻塞)

复制代码
UPDATE A SET ... WHERE id = 1;

三、(行锁)锁表会影响其他表吗

A 表发生写锁(行锁 / 表锁)

❌ 不会直接影响其他表(B、C...)的读写

✅ 但会"间接影响"其他表,常见于连接、事务、线程被拖住的情况

A 表被锁 │

└──────┬───────┘

┌───────────┼───────────┐

▼ ▼ ▼

同一事务 连接池耗尽 MySQL连接数满

锁住B/C表 │ │

│ ┌───┴───┐ ┌──┴──┐

▼ ▼ ▼ ▼ ▼

B/C表 B表查询 C表 所有表 所有表

阻塞 超时 超时 拒绝连接 拒绝连接

1、数据库连接池耗尽

复制代码
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Threads_running';

验证方法:看应用日志里是否有类似 Cannot get a connection, pool error Timeout waiting for idle object 的报错。会受影响的是"所有连到同一 MySQL 实例的 Pod",不是"只有同一个 Pod"。

2、MySQL Server 层线程耗尽

复制代码
SHOW VARIABLES LIKE 'max_connections';   -- 最大连接数
SHOW STATUS LIKE 'Threads_connected';    -- 当前连接数
相关推荐
沧澜sincerely1 小时前
组合查询(UNION)
数据库·union·union all
爬山算法2 小时前
MongoDB(10)如何安装MongoDB?
数据库·mongodb
yixin1232 小时前
【玩转全栈】----Django基本配置和介绍
数据库·django·sqlite
zhangyueping83852 小时前
4、MYSQL-DQL-基本查询
数据库·mysql
ID_180079054732 小时前
Python采集京东商品详情:基于官方API的规格与价格获取
开发语言·数据库·python
w_t_y_y2 小时前
数据库连接池(一)HikariCP
数据库
sheji70093 小时前
Springboot家教平台中心系统53754--(程序+源码+数据库+调试部署+开发环境)
java·数据库·spring boot·后端·spring·旅游
小宋10214 小时前
Java 数据库访问 vs Python 数据库访问:JDBC vs ORM
java·数据库·python