死锁从基础到架构实战

1. 概述

1.1 死锁的重要性

死锁(Deadlock)是数据库并发控制中的核心问题,当多个事务相互等待对方释放资源时,就会发生死锁。理解死锁机制、掌握死锁的检测、预防和解决方法是数据库运维和架构设计的必备技能。

死锁的价值

  • 系统稳定性:死锁会导致事务无法完成,影响系统可用性
  • 性能优化:合理处理死锁可以提升系统性能
  • 数据一致性:死锁处理不当可能导致数据不一致
  • 架构设计:理解死锁有助于设计更好的并发架构

1.2 死锁的定义

死锁:两个或多个事务在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

死锁的四个必要条件(Coffman条件):

  1. 互斥条件:资源不能被多个事务同时使用
  2. 请求与保持条件:事务持有资源的同时请求其他资源
  3. 不剥夺条件:已获得的资源不能被强制释放
  4. 循环等待条件:存在事务资源的循环等待链

1.3 本文内容结构

本文将从以下几个方面全面解析死锁:

  1. 死锁基础:死锁概念、产生条件、死锁类型
  2. MySQL死锁:死锁检测、日志分析、解决方案
  3. Oracle死锁:死锁检测、处理机制、优化建议
  4. PostgreSQL死锁:死锁检测、处理方式、最佳实践
  5. SQL Server死锁:死锁检测、图形化分析、解决方案
  6. 其他数据库:MongoDB、Redis等数据库的死锁处理
  7. 死锁预防:设计原则、最佳实践
  8. 死锁排查:排查工具、分析方法
  9. 架构实战:高并发场景下的死锁处理

2. 死锁基础

2.1 死锁的概念

2.1.1 什么是死锁

死锁定义
死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种互相等待的现象。如果没有外力干预,这些事务将永远无法继续执行。

死锁示例

1
2
3
4
5
6
7
8
9
事务A:
1. 锁定资源X
2. 请求资源Y(等待)

事务B:
1. 锁定资源Y
2. 请求资源X(等待)

结果:事务A等待事务B释放Y,事务B等待事务A释放X → 死锁

2.1.2 死锁 vs 锁等待

锁等待(Lock Wait)

  • 一个事务等待另一个事务释放锁
  • 是正常的并发控制机制
  • 等待的事务最终会获得锁

死锁(Deadlock)

  • 多个事务相互等待
  • 是异常情况
  • 需要数据库自动检测并解决

2.2 死锁产生的必要条件

2.2.1 Coffman条件

1. 互斥条件(Mutual Exclusion)

  • 资源不能被多个事务同时使用
  • 一个资源在同一时刻只能被一个事务持有

2. 请求与保持条件(Hold and Wait)

  • 事务在持有资源的同时请求其他资源
  • 不会释放已持有的资源

3. 不剥夺条件(No Preemption)

  • 已获得的资源不能被强制释放
  • 只能由持有资源的事务主动释放

4. 循环等待条件(Circular Wait)

  • 存在事务资源的循环等待链
  • T1等待T2,T2等待T3,…,Tn等待T1

说明

  • 四个条件必须同时满足才会发生死锁
  • 只要破坏其中一个条件,就可以避免死锁

2.3 死锁的类型

2.3.1 按资源类型分类

表级死锁

  • 多个事务争夺表级锁
  • 常见于DDL操作

行级死锁

  • 多个事务争夺同一行或不同行的锁
  • 最常见死锁类型

页级死锁

  • 多个事务争夺数据页锁
  • 较少见

2.3.2 按等待关系分类

直接死锁

  • 两个事务直接相互等待
  • T1等待T2,T2等待T1

间接死锁

  • 多个事务形成循环等待
  • T1等待T2,T2等待T3,T3等待T1

2.4 死锁的影响

2.4.1 对系统的影响

性能影响

  • 死锁检测和处理消耗CPU资源
  • 被回滚的事务需要重试
  • 影响系统吞吐量

可用性影响

  • 死锁导致事务无法完成
  • 用户请求失败
  • 影响用户体验

数据一致性

  • 死锁回滚可能导致数据不一致
  • 需要应用层处理回滚逻辑

3. MySQL死锁

3.1 MySQL死锁检测

3.1.1 死锁检测机制

MySQL死锁检测

  • InnoDB引擎自动检测死锁
  • 使用等待图(Wait-for Graph)算法
  • 检测到死锁后自动回滚一个事务

检测频率

  • 每次锁等待时检测
  • 检测到死锁立即处理

3.1.2 查看死锁信息

查看最近死锁信息

1
2
-- 查看InnoDB状态(包含死锁信息)
SHOW ENGINE INNODB STATUS\G

查看死锁日志

1
2
3
4
5
-- 查看死锁相关配置
SHOW VARIABLES LIKE 'innodb_print_all_deadlocks';

-- 启用死锁日志(记录所有死锁到错误日志)
SET GLOBAL innodb_print_all_deadlocks = ON;

查看错误日志

1
2
# 查看MySQL错误日志
tail -f /var/log/mysql/error.log | grep -i deadlock

3.2 MySQL死锁日志分析

3.2.1 死锁日志格式

典型死锁日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
LATEST DETECTED DEADLOCK
------------------------
2024-01-15 10:30:45 0x7f8b8c0b9700
*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 5 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 10, OS thread handle 140123456789, query id 100 localhost root updating
UPDATE users SET name = 'Alice' WHERE id = 1

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1 page no 3 n bits 72 index PRIMARY of table `test`.`users` trx id 12345 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 00000001; asc ;;
1: len 6; hex 000000000303; asc ;;
2: len 7; hex 82000001010110; asc ;;

*** (2) TRANSACTION:
TRANSACTION 12346, ACTIVE 3 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 11, OS thread handle 140123456790, query id 101 localhost root updating
UPDATE users SET name = 'Bob' WHERE id = 2

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1 page no 3 n bits 72 index PRIMARY of table `test`.`users` trx id 12346 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 00000002; asc ;;
1: len 6; hex 000000000304; asc ;;
2: len 7; hex 82000001020120; asc ;;

*** WE ROLL BACK TRANSACTION (2)

3.2.2 日志解读

关键信息

  • TRANSACTION:事务ID
  • LOCK WAIT:锁等待信息
  • WAITING FOR THIS LOCK:等待的锁
  • WE ROLL BACK TRANSACTION:回滚的事务

锁类型

  • X locks:排他锁
  • S locks:共享锁
  • rec but not gap:记录锁,非间隙锁
  • gap:间隙锁
  • next-key:Next-Key锁

3.3 MySQL死锁场景

3.3.1 场景1:相同资源不同顺序

1
2
3
4
5
6
7
8
9
10
11
-- 事务A
BEGIN;
UPDATE users SET name = 'A' WHERE id = 1;
UPDATE users SET name = 'A' WHERE id = 2;
COMMIT;

-- 事务B(同时执行)
BEGIN;
UPDATE users SET name = 'B' WHERE id = 2;
UPDATE users SET name = 'B' WHERE id = 1;
COMMIT;

死锁原因

  • 事务A锁定id=1,请求id=2
  • 事务B锁定id=2,请求id=1
  • 形成循环等待

解决方案

  • 统一锁定顺序(都先锁id=1,再锁id=2)
  • 使用应用层锁

3.3.2 场景2:Gap锁死锁

1
2
3
4
5
6
7
8
9
-- 事务A
BEGIN;
SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;
INSERT INTO users (id, name) VALUES (15, 'New');

-- 事务B
BEGIN;
SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;
INSERT INTO users (id, name) VALUES (16, 'New');

死锁原因

  • Gap锁冲突
  • 两个事务都持有Gap锁,插入时冲突

解决方案

  • 使用唯一索引
  • 减少Gap锁范围

3.3.3 场景3:外键死锁

1
2
3
4
5
6
7
8
9
-- 事务A
BEGIN;
INSERT INTO orders (user_id, amount) VALUES (1, 100);
UPDATE users SET balance = balance - 100 WHERE id = 1;

-- 事务B
BEGIN;
UPDATE users SET balance = balance + 50 WHERE id = 1;
INSERT INTO orders (user_id, amount) VALUES (1, 50);

死锁原因

  • 外键约束导致额外的锁
  • 锁定顺序不一致

解决方案

  • 统一操作顺序
  • 使用应用层事务控制

3.4 MySQL死锁解决方案

3.4.1 应用层处理

重试机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void updateWithRetry(int maxRetries) {
int retries = 0;
while (retries < maxRetries) {
try {
// 执行数据库操作
updateData();
return;
} catch (DeadlockLoserDataAccessException e) {
retries++;
if (retries >= maxRetries) {
throw e;
}
// 随机延迟后重试
Thread.sleep((long)(Math.random() * 100));
}
}
}

统一锁定顺序

1
2
3
4
5
6
7
// 按照ID排序,统一锁定顺序
List<Integer> ids = Arrays.asList(2, 1, 3);
ids.sort(Integer::compareTo); // 排序

for (Integer id : ids) {
updateById(id);
}

3.4.2 数据库层优化

减少事务时间

1
2
3
4
5
-- 快速提交事务
BEGIN;
-- 只做必要的操作
UPDATE ...;
COMMIT; -- 尽快提交

使用合适的隔离级别

1
2
-- 使用READ COMMITTED减少锁冲突
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

优化索引

1
2
-- 使用唯一索引减少Gap锁
CREATE UNIQUE INDEX idx_user_id ON users(id);

4. Oracle死锁

4.1 Oracle死锁检测

4.1.1 死锁检测机制

Oracle死锁检测

  • 自动检测死锁
  • 使用等待图算法
  • 检测到死锁后回滚一个事务

检测频率

  • 每3秒检测一次
  • 检测到死锁立即处理

4.1.2 查看死锁信息

查看死锁信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 查看当前死锁
SELECT * FROM v$lock WHERE block > 0;

-- 查看死锁详细信息
SELECT
s.sid,
s.serial#,
s.username,
s.program,
l.type,
l.id1,
l.id2,
l.lmode,
l.request,
l.block
FROM v$session s, v$lock l
WHERE s.sid = l.sid
AND l.block > 0;

查看死锁历史

1
2
3
4
-- 查看告警日志中的死锁信息
SELECT * FROM v$diag_alert_ext
WHERE message_text LIKE '%deadlock%'
ORDER BY originating_timestamp DESC;

4.2 Oracle死锁场景

4.2.1 场景1:表锁死锁

1
2
3
4
5
6
7
-- 会话A
LOCK TABLE users IN EXCLUSIVE MODE;
-- 等待其他操作

-- 会话B
LOCK TABLE users IN EXCLUSIVE MODE;
-- 等待会话A释放锁

解决方案

  • 避免长时间持有表锁
  • 使用行级锁替代表级锁

4.2.2 场景2:外键死锁

1
2
3
4
5
6
7
-- 会话A
UPDATE parent SET name = 'A' WHERE id = 1;
UPDATE child SET name = 'A' WHERE parent_id = 1;

-- 会话B
UPDATE child SET name = 'B' WHERE parent_id = 1;
UPDATE parent SET name = 'B' WHERE id = 1;

解决方案

  • 统一操作顺序(先父表后子表)
  • 使用延迟约束检查

4.3 Oracle死锁处理

4.3.1 手动处理死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 查找死锁会话
SELECT
s.sid,
s.serial#,
s.username,
s.status
FROM v$session s, v$lock l1, v$lock l2
WHERE s.sid = l1.sid
AND l1.id1 = l2.id1
AND l1.id2 = l2.id2
AND l1.request > 0
AND l2.lmode > 0;

-- 杀死死锁会话
ALTER SYSTEM KILL SESSION 'sid,serial#';

4.3.2 预防死锁

统一锁定顺序

1
2
-- 按照ID排序锁定
SELECT * FROM users WHERE id IN (2, 1, 3) ORDER BY id FOR UPDATE;

减少锁定时间

1
2
3
4
5
-- 快速提交
BEGIN
-- 操作
COMMIT;
END;

5. PostgreSQL死锁

5.1 PostgreSQL死锁检测

5.1.1 死锁检测机制

PostgreSQL死锁检测

  • 自动检测死锁
  • 使用等待图算法
  • 检测到死锁后回滚一个事务

检测频率

  • 默认每1秒检测一次
  • 可通过deadlock_timeout参数调整

5.1.2 查看死锁信息

查看死锁配置

1
2
3
4
5
-- 查看死锁超时时间
SHOW deadlock_timeout;

-- 设置死锁超时时间(毫秒)
SET deadlock_timeout = '1s';

查看死锁日志

1
2
# 查看PostgreSQL日志
tail -f /var/log/postgresql/postgresql.log | grep -i deadlock

查看当前锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 查看当前锁信息
SELECT
locktype,
database,
relation,
page,
tuple,
virtualxid,
transactionid,
classid,
objid,
objsubid,
virtualtransaction,
pid,
mode,
granted
FROM pg_locks
WHERE NOT granted;

5.2 PostgreSQL死锁场景

5.2.1 场景1:行锁死锁

1
2
3
4
5
6
7
8
9
10
11
-- 事务A
BEGIN;
UPDATE users SET name = 'A' WHERE id = 1;
UPDATE users SET name = 'A' WHERE id = 2;
COMMIT;

-- 事务B
BEGIN;
UPDATE users SET name = 'B' WHERE id = 2;
UPDATE users SET name = 'B' WHERE id = 1;
COMMIT;

5.2.2 场景2:表锁死锁

1
2
3
4
5
6
7
8
9
10
11
-- 事务A
BEGIN;
LOCK TABLE users IN ACCESS EXCLUSIVE MODE;
-- 执行操作
COMMIT;

-- 事务B
BEGIN;
LOCK TABLE users IN ACCESS EXCLUSIVE MODE;
-- 执行操作
COMMIT;

5.3 PostgreSQL死锁处理

5.3.1 应用层处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import psycopg2
from psycopg2 import OperationalError

def execute_with_retry(conn, query, max_retries=3):
retries = 0
while retries < max_retries:
try:
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
return cursor.fetchall()
except OperationalError as e:
if 'deadlock' in str(e).lower():
retries += 1
if retries >= max_retries:
raise
time.sleep(0.1 * retries) # 指数退避
else:
raise

5.3.2 数据库层优化

使用合适的锁模式

1
2
3
4
5
-- 使用行级锁替代表级锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;

-- 使用共享锁
SELECT * FROM users WHERE id = 1 FOR SHARE;

6. SQL Server死锁

6.1 SQL Server死锁检测

6.1.1 死锁检测机制

SQL Server死锁检测

  • 自动检测死锁
  • 使用等待图算法
  • 选择代价最小的事务回滚

死锁优先级

1
2
3
4
-- 设置死锁优先级
SET DEADLOCK_PRIORITY LOW; -- 低优先级,容易被选为牺牲者
SET DEADLOCK_PRIORITY NORMAL; -- 正常优先级
SET DEADLOCK_PRIORITY HIGH; -- 高优先级,不容易被选为牺牲者

6.1.2 查看死锁信息

启用死锁跟踪

1
2
3
-- 启用死锁跟踪标志
DBCC TRACEON(1222, -1); -- 输出到错误日志
DBCC TRACEON(1204, -1); -- 输出到错误日志(详细)

查看死锁图

1
2
3
4
5
6
7
8
9
-- 使用扩展事件捕获死锁
CREATE EVENT SESSION DeadlockGraph ON SERVER
ADD EVENT sqlserver.xml_deadlock_report
ADD TARGET package0.event_file(
SET filename = N'C:\temp\deadlock.xel'
)
WITH (MAX_DISPATCH_LATENCY = 5 SECONDS);

ALTER EVENT SESSION DeadlockGraph ON SERVER STATE = START;

查看死锁信息

1
2
3
4
5
6
7
8
9
-- 查看当前锁
SELECT
request_session_id,
resource_type,
resource_database_id,
resource_associated_entity_id,
request_mode,
request_status
FROM sys.dm_tran_locks;

6.2 SQL Server死锁场景

6.2.1 场景1:索引死锁

1
2
3
4
5
6
7
8
9
10
11
-- 事务A
BEGIN TRANSACTION;
UPDATE users SET name = 'A' WHERE id = 1;
UPDATE users SET name = 'A' WHERE id = 2;
COMMIT;

-- 事务B
BEGIN TRANSACTION;
UPDATE users SET name = 'B' WHERE id = 2;
UPDATE users SET name = 'B' WHERE id = 1;
COMMIT;

6.2.2 场景2:锁升级死锁

1
2
3
4
5
6
7
8
9
-- 事务A(锁定大量行,触发锁升级)
BEGIN TRANSACTION;
UPDATE users SET status = 1 WHERE id BETWEEN 1 AND 10000;
-- 锁升级为表锁

-- 事务B(需要表锁)
BEGIN TRANSACTION;
ALTER TABLE users ADD COLUMN new_col INT;
-- 等待表锁

6.3 SQL Server死锁处理

6.3.1 使用死锁优先级

1
2
3
4
5
6
-- 设置死锁优先级
SET DEADLOCK_PRIORITY HIGH;

BEGIN TRANSACTION;
-- 操作
COMMIT;

6.3.2 使用NOLOCK提示(谨慎使用)

1
2
-- 读取未提交数据(脏读)
SELECT * FROM users WITH (NOLOCK) WHERE id = 1;

注意:NOLOCK可能导致脏读,不推荐使用。


7. 其他数据库死锁

7.1 MongoDB死锁

7.1.1 MongoDB锁机制

MongoDB锁

  • 全局写锁(早期版本)
  • 文档级锁(WiredTiger引擎)
  • 集合级锁(MMAPv1引擎)

7.1.2 MongoDB死锁场景

1
2
3
4
5
6
7
8
9
10
11
// 事务A
session.startTransaction();
db.users.updateOne({id: 1}, {$set: {name: 'A'}});
db.orders.insertOne({user_id: 1, amount: 100});
session.commitTransaction();

// 事务B
session.startTransaction();
db.orders.insertOne({user_id: 1, amount: 50});
db.users.updateOne({id: 1}, {$set: {name: 'B'}});
session.commitTransaction();

解决方案

  • 统一操作顺序
  • 使用应用层锁

7.2 Redis死锁

7.2.1 Redis锁机制

Redis锁

  • 单线程模型,不存在传统死锁
  • 但可能出现业务层面的死锁

7.2.2 Redis分布式锁死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 获取锁
String lockKey = "lock:resource:1";
String lockValue = UUID.randomUUID().toString();
Boolean acquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, 30, TimeUnit.SECONDS);

try {
// 业务逻辑
if (acquired) {
// 处理业务
}
} finally {
// 释放锁(需要检查lockValue)
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey), lockValue);
}

死锁预防

  • 设置锁过期时间
  • 使用唯一值标识锁
  • 实现锁续期机制

7.3 其他数据库

7.3.1 DB2死锁

1
2
3
4
5
-- 查看死锁信息
SELECT * FROM SYSIBMADM.SNAPDB;

-- 查看锁信息
SELECT * FROM SYSIBMADM.LOCKS_HELD;

7.3.2 Informix死锁

1
2
-- 查看死锁信息
SELECT * FROM sysmaster:syslocks;

8. 死锁预防

8.1 设计原则

8.1.1 统一锁定顺序

原则

  • 所有事务按照相同的顺序获取锁
  • 避免循环等待

实现方式

1
2
3
4
5
6
7
// 按照ID排序锁定
List<Integer> ids = getIdsToUpdate();
ids.sort(Integer::compareTo); // 排序

for (Integer id : ids) {
updateById(id);
}

8.1.2 减少锁定时间

原则

  • 尽快释放锁
  • 减少事务时间

实现方式

1
2
3
4
5
6
7
8
9
10
11
// 快速提交
try {
connection.setAutoCommit(false);
// 只做必要的操作
updateData();
connection.commit(); // 尽快提交
} catch (SQLException e) {
connection.rollback();
} finally {
connection.setAutoCommit(true);
}

8.1.3 使用合适的隔离级别

隔离级别选择

隔离级别 死锁风险 性能 数据一致性
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE 最高 最低 最好

建议

  • 大多数场景使用READ COMMITTED
  • 需要强一致性时使用REPEATABLE READ
  • 避免使用SERIALIZABLE(除非必要)

8.2 最佳实践

8.2.1 应用层实践

1. 使用应用层锁

1
2
3
4
5
6
7
8
9
10
11
12
// 使用分布式锁
public void updateWithLock(String resourceId) {
String lockKey = "lock:" + resourceId;
if (distributedLock.tryLock(lockKey, 10, TimeUnit.SECONDS)) {
try {
// 业务逻辑
updateResource(resourceId);
} finally {
distributedLock.unlock(lockKey);
}
}
}

2. 实现重试机制

1
2
3
4
5
6
@Retryable(value = {DeadlockLoserDataAccessException.class}, 
maxAttempts = 3,
backoff = @Backoff(delay = 100))
public void updateWithRetry() {
// 数据库操作
}

3. 批量操作优化

1
2
3
4
5
6
7
8
9
10
// 批量更新,减少锁竞争
public void batchUpdate(List<Integer> ids) {
// 分批处理
int batchSize = 100;
for (int i = 0; i < ids.size(); i += batchSize) {
List<Integer> batch = ids.subList(i,
Math.min(i + batchSize, ids.size()));
updateBatch(batch);
}
}

8.2.2 数据库层实践

1. 优化索引

1
2
3
4
5
-- 使用唯一索引减少Gap锁
CREATE UNIQUE INDEX idx_user_id ON users(id);

-- 避免全表扫描
CREATE INDEX idx_status ON users(status);

2. 分区表

1
2
3
4
5
6
7
8
9
10
-- 使用分区表减少锁竞争
CREATE TABLE orders (
id INT,
user_id INT,
amount DECIMAL(10,2)
) PARTITION BY RANGE (user_id) (
PARTITION p0 VALUES LESS THAN (1000),
PARTITION p1 VALUES LESS THAN (2000),
PARTITION p2 VALUES LESS THAN MAXVALUE
);

3. 读写分离

1
2
3
4
5
-- 读操作使用从库
SELECT * FROM users WHERE id = 1; -- 从库

-- 写操作使用主库
UPDATE users SET name = 'A' WHERE id = 1; -- 主库

9. 死锁排查

9.1 排查工具

9.1.1 MySQL排查工具

pt-deadlock-logger

1
2
3
4
5
6
7
# 安装Percona Toolkit
yum install percona-toolkit

# 监控死锁
pt-deadlock-logger --user=root --password=xxx \
--host=localhost --create-dest-table \
--dest D=test,t=deadlocks

查看死锁统计

1
2
3
4
5
6
-- 查看死锁统计
SELECT
COUNT(*) as deadlock_count,
MIN(ts) as first_deadlock,
MAX(ts) as last_deadlock
FROM deadlocks;

9.1.2 Oracle排查工具

AWR报告

1
2
-- 生成AWR报告
@?/rdbms/admin/awrrpt.sql

ASH报告

1
2
-- 生成ASH报告
@?/rdbms/admin/ashrpt.sql

9.1.3 PostgreSQL排查工具

pg_stat_statements

1
2
3
4
5
6
7
-- 启用pg_stat_statements
CREATE EXTENSION pg_stat_statements;

-- 查看慢查询(可能包含死锁相关)
SELECT * FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;

9.2 排查方法

9.2.1 死锁日志分析

分析步骤

  1. 收集死锁日志
  2. 识别死锁事务
  3. 分析锁定顺序
  4. 找出根本原因
  5. 制定解决方案

分析工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import re
from collections import defaultdict

def analyze_deadlock_log(log_file):
"""分析死锁日志"""
deadlocks = []
current_deadlock = {}

with open(log_file, 'r') as f:
for line in f:
if 'LATEST DETECTED DEADLOCK' in line:
if current_deadlock:
deadlocks.append(current_deadlock)
current_deadlock = {}
elif 'TRANSACTION' in line:
# 提取事务信息
pass
elif 'WAITING FOR' in line:
# 提取等待信息
pass

return deadlocks

9.2.2 实时监控

监控脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
# 监控MySQL死锁

while true; do
# 检查死锁
mysql -u root -p -e "SHOW ENGINE INNODB STATUS\G" | \
grep -A 50 "LATEST DETECTED DEADLOCK" > /tmp/deadlock.log

if [ -s /tmp/deadlock.log ]; then
echo "发现死锁!"
cat /tmp/deadlock.log
# 发送告警
send_alert "发现数据库死锁"
fi

sleep 10
done

10. 架构实战

10.1 高并发场景设计

10.1.1 分布式锁方案

Redis分布式锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class DistributedLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;

public boolean tryLock(String key, String value, long expireTime) {
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}

public void unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
value
);
}
}

Zookeeper分布式锁

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ZookeeperLock {
private CuratorFramework client;

public InterProcessMutex acquireLock(String path) {
InterProcessMutex lock = new InterProcessMutex(client, path);
try {
lock.acquire(10, TimeUnit.SECONDS);
return lock;
} catch (Exception e) {
throw new RuntimeException("获取锁失败", e);
}
}
}

10.1.2 数据库连接池优化

HikariCP配置

1
2
3
4
5
6
7
8
9
# 连接池配置
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000

# 死锁相关
spring.datasource.hikari.leak-detection-threshold=60000

10.1.3 事务管理优化

使用声明式事务

1
2
3
4
5
6
7
8
9
10
11
12
@Service
@Transactional(
isolation = Isolation.READ_COMMITTED,
propagation = Propagation.REQUIRED,
timeout = 5,
rollbackFor = Exception.class
)
public class UserService {
public void updateUser(User user) {
// 业务逻辑
}
}

10.2 实战案例

10.2.1 案例1:订单系统死锁

场景

  • 高并发下单
  • 库存扣减
  • 账户余额更新

问题

  • 多个订单同时操作同一用户账户
  • 锁定顺序不一致导致死锁

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class OrderService {
@Autowired
private DistributedLock distributedLock;

public void createOrder(Order order) {
// 按照用户ID排序锁定
String lockKey = "lock:user:" + order.getUserId();
String lockValue = UUID.randomUUID().toString();

if (distributedLock.tryLock(lockKey, lockValue, 10)) {
try {
// 1. 扣减库存
reduceStock(order.getProductId(), order.getQuantity());

// 2. 扣减余额
reduceBalance(order.getUserId(), order.getAmount());

// 3. 创建订单
saveOrder(order);
} finally {
distributedLock.unlock(lockKey, lockValue);
}
} else {
throw new BusinessException("系统繁忙,请稍后重试");
}
}
}

10.2.2 案例2:账户转账死锁

场景

  • 账户A向账户B转账
  • 账户B向账户A转账
  • 同时发生导致死锁

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
public class TransferService {
public void transfer(int fromAccountId, int toAccountId, BigDecimal amount) {
// 统一锁定顺序:按照账户ID排序
int firstLock = Math.min(fromAccountId, toAccountId);
int secondLock = Math.max(fromAccountId, toAccountId);

// 先锁小ID,再锁大ID
lockAccount(firstLock);
try {
lockAccount(secondLock);
try {
// 执行转账
deductBalance(fromAccountId, amount);
addBalance(toAccountId, amount);
} finally {
unlockAccount(secondLock);
}
} finally {
unlockAccount(firstLock);
}
}
}

11. 总结

11.1 核心要点

  1. 死锁基础:四个必要条件、死锁类型
  2. MySQL死锁:检测机制、日志分析、解决方案
  3. Oracle死锁:检测机制、处理方式
  4. PostgreSQL死锁:检测机制、最佳实践
  5. SQL Server死锁:死锁图、优先级设置
  6. 其他数据库:MongoDB、Redis等
  7. 死锁预防:统一锁定顺序、减少锁定时间
  8. 死锁排查:工具和方法
  9. 架构实战:高并发场景下的死锁处理

11.2 架构师建议

  1. 设计原则

    • 统一锁定顺序
    • 减少锁定时间
    • 使用合适的隔离级别
  2. 预防措施

    • 应用层锁
    • 重试机制
    • 批量操作优化
  3. 监控告警

    • 实时监控死锁
    • 分析死锁日志
    • 及时处理死锁

11.3 最佳实践

  1. 统一标准:所有事务按照相同顺序获取锁
  2. 快速提交:尽快释放锁,减少事务时间
  3. 合理隔离:根据业务需求选择合适的隔离级别
  4. 监控告警:实时监控死锁,及时处理

相关文章