Skip to content

排他锁 (Exclusive Lock / X Lock) ​

📋 概述 ​

排他锁(Exclusive Lock),又称写锁(Write Lock)或 X 锁,是一种独占式访问控制机制。当一个事务获得某资源的排他锁后,其他事务不能获得该资源的任何类型的锁(包括共享锁和排他锁)。

核心特点 ​

✅ 优点:

  • 数据一致性: 确保修改期间数据不被其他事务访问
  • 隔离性强: 完全隔离并发事务
  • 防止脏写: 避免多个事务同时修改同一数据
  • 简单直观: 易于理解和实现

❌ 缺点:

  • 并发度低: 阻塞所有其他事务的访问
  • 可能死锁: 多个事务互相等待对方释放锁
  • 性能影响: 长事务持有会严重影响并发
  • 资源浪费: 即使只修改一个字段也锁定整行

适用场景 ​

  • 数据修改: UPDATE、DELETE 操作
  • 数据插入: INSERT 操作
  • 一致性更新: 读取后立即修改的场景
  • 临界区保护: 需要独占访问的代码段
  • 事务隔离: 确保事务串行执行

🔧 工作原理 ​

锁兼容性矩阵 ​

排他锁是最严格的锁模式:

        | 无锁  | S锁   | X锁
--------|-------|-------|------
X锁    | ✅    | ❌    | ❌

解读:

  • X 锁与任何锁都不兼容(除了无锁状态)
  • 一旦获得 X 锁,其他事务必须等待

示例:

事务A: SELECT ... FOR UPDATE;  -- 获得 X 锁
事务B: SELECT ... FOR UPDATE;  -- ❌ 必须等待
事务C: SELECT ... LOCK IN SHARE MODE;  -- ❌ 必须等待
事务D: UPDATE ...;  -- ❌ 必须等待

自动获取排他锁 ​

DML 操作会自动获取排他锁:

操作锁行为说明
INSERTX 锁插入新行时加排他锁
UPDATEX 锁更新行时加排他锁
DELETEX 锁删除行时加排他锁
sql
-- 这些操作都会自动加排他锁
UPDATE users SET name = 'test' WHERE id = 1;  -- 自动加 X 锁
DELETE FROM users WHERE id = 1;               -- 自动加 X 锁
INSERT INTO users VALUES (1, 'john');         -- 自动加 X 锁

显式获取排他锁 ​

sql
-- MySQL
SELECT * FROM users WHERE id = 1 FOR UPDATE;

-- PostgreSQL
SELECT * FROM users WHERE id = 1 FOR UPDATE;

-- SQL Server
SELECT * FROM users WITH (UPDLOCK, ROWLOCK) WHERE id = 1;

-- Oracle
SELECT * FROM users WHERE id = 1 FOR UPDATE;

💻 各数据库实现 ​

MySQL ​

基本用法 ​

sql
-- 显式加排他锁
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- 多行排他锁
SELECT * FROM users WHERE age > 18 FOR UPDATE;

-- 不等待立即返回
SELECT * FROM users WHERE id = 1 FOR UPDATE NOWAIT;

-- 跳过被锁定的行
SELECT * FROM users WHERE id IN (1,2,3) FOR UPDATE SKIP LOCKED;

锁的查看 ​

sql
-- 查看排他锁
SELECT 
    lock_id,
    lock_trx_id,
    lock_mode,
    lock_type,
    lock_table,
    lock_index,
    lock_data
FROM performance_schema.data_locks
WHERE lock_mode = 'X';  -- X 表示排他锁

-- 查看锁等待
SELECT * FROM performance_schema.data_lock_waits;

-- InnoDB 状态
SHOW ENGINE INNODB STATUS\G

参数配置 ​

ini
# my.cnf
# 排他锁等待超时时间(秒)
innodb_lock_wait_timeout = 50

# 死锁检测
innodb_deadlock_detect = ON

PostgreSQL ​

基本用法 ​

sql
-- 显式加排他锁
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- 更强的锁模式
SELECT * FROM users WHERE id = 1 FOR NO KEY UPDATE;  -- 不影响外键
SELECT * FROM users WHERE id = 1 FOR SHARE;          -- 共享锁
SELECT * FROM users WHERE id = 1 FOR KEY SHARE;      -- 最弱

-- 不等待
SELECT * FROM users WHERE id = 1 FOR UPDATE NOWAIT;

-- 跳过被锁定的行
SELECT * FROM users WHERE id IN (1,2,3) FOR UPDATE SKIP LOCKED;

MVCC 与排他锁 ​

PostgreSQL 使用 MVCC,但写操作仍需加排他锁:

sql
-- 事务 A
BEGIN;
UPDATE users SET name = 'alice' WHERE id = 1;
-- 持有排他锁,但未提交

-- 事务 B
SELECT * FROM users WHERE id = 1;  
-- ✅ 不被阻塞,读到旧版本(MVCC)

UPDATE users SET name = 'bob' WHERE id = 1;
-- ❌ 被阻塞,等待事务 A 提交或回滚

查看排他锁 ​

sql
-- 查看所有排他锁
SELECT 
    l.locktype,
    l.relation::regclass AS table_name,
    l.mode,
    l.granted,
    a.pid,
    a.usename,
    a.query
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE l.mode IN ('ExclusiveLock', 'RowExclusiveLock')
ORDER BY l.relation;

SQL Server ​

基本用法 ​

sql
-- 使用排他锁提示
BEGIN TRANSACTION;
    SELECT * FROM users WITH (UPDLOCK, ROWLOCK) WHERE id = 1;
    UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT TRANSACTION;

-- 强制使用排他锁
UPDATE users WITH (ROWLOCK) SET name = 'test' WHERE id = 1;

-- 表级排他锁
SELECT * FROM users WITH (TABLOCKX);

查看排他锁 ​

sql
-- 查看当前的排他锁
SELECT 
    tl.request_session_id,
    OBJECT_NAME(p.object_id) AS table_name,
    tl.resource_type,
    tl.request_mode,
    tl.request_status
FROM sys.dm_tran_locks tl
JOIN sys.partitions p ON tl.resource_associated_entity_id = p.hobt_id
WHERE tl.request_mode IN ('X', 'IX');  -- X=排他锁, IX=意向排他锁

Oracle ​

基本用法 ​

sql
-- 显式加排他锁
BEGIN
    SELECT * INTO user_record FROM users WHERE id = 1 FOR UPDATE;
    UPDATE users SET balance = balance - 100 WHERE id = 1;
    COMMIT;
END;

-- 不等待
SELECT * FROM users WHERE id = 1 FOR UPDATE NOWAIT;

-- 指定等待时间
SELECT * FROM users WHERE id = 1 FOR UPDATE WAIT 10;

-- 跳过已锁定的行
SELECT * FROM users WHERE id IN (1,2,3) FOR UPDATE SKIP LOCKED;

查看排他锁 ​

sql
-- 查看行级排他锁
SELECT 
    s.sid,
    s.serial#,
    s.username,
    l.type,
    l.lmode,
    l.request,
    o.object_name
FROM v$session s
JOIN v$lock l ON s.sid = l.sid
JOIN dba_objects o ON l.id1 = o.object_id
WHERE l.type = 'TX'  -- TX = 事务锁(行级排他锁)
ORDER BY o.object_name;

📊 性能影响分析 ​

并发度评估 ​

场景并发度说明
不同行的操作⭐⭐⭐⭐⭐完全并行
同一行的操作⭐串行执行
范围查询⭐⭐锁定多行
全表更新⭐几乎串行

性能测试示例 ​

sql
-- 测试场景:100个并发事务更新同一行

-- 使用排他锁
-- 100个事务串行执行,总耗时:~50秒
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- 更新不同行
-- 100个事务并行执行,总耗时:~1秒
BEGIN;
SELECT * FROM users WHERE id = ? FOR UPDATE;  -- 不同的 id
UPDATE users SET balance = balance - 100 WHERE id = ?;
COMMIT;

🎯 最佳实践 ​

✅ 推荐做法 ​

1. 缩短事务时间 ​

sql
-- ❌ 不推荐:长时间持有排他锁
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- 调用外部 API(耗时操作)
CALL process_order(1);
UPDATE orders SET status = 'processed' WHERE id = 1;
COMMIT;

-- ✅ 推荐:快速完成
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
UPDATE orders SET status = 'processed' WHERE id = 1;
COMMIT;
-- 之后再调用外部 API
CALL process_order(1);

2. 固定加锁顺序避免死锁 ​

sql
-- ❌ 可能导致死锁
-- 事务 A
SELECT * FROM users WHERE id = 1 FOR UPDATE;
SELECT * FROM users WHERE id = 2 FOR UPDATE;

-- 事务 B
SELECT * FROM users WHERE id = 2 FOR UPDATE;
SELECT * FROM users WHERE id = 1 FOR UPDATE;

-- ✅ 固定顺序
SELECT * FROM users WHERE id IN (1,2) ORDER BY id FOR UPDATE;

3. 批量操作优化 ​

sql
-- ❌ 逐行处理
FOR i IN 1..1000 LOOP
    SELECT * FROM users WHERE id = i FOR UPDATE;
    UPDATE users SET ... WHERE id = i;
END LOOP;

-- ✅ 批量处理
SELECT * FROM users WHERE id BETWEEN 1 AND 1000 FOR UPDATE;
UPDATE users SET ... WHERE id BETWEEN 1 AND 1000;

4. 设置合理的超时 ​

sql
-- MySQL
SET innodb_lock_wait_timeout = 10;

-- PostgreSQL
SET lock_timeout = '5s';

-- SQL Server
SET LOCK_TIMEOUT 5000;

❌ 避免陷阱 ​

1. 避免忽略死锁异常 ​

python
# ❌ 不处理死锁
cursor.execute("SELECT * FROM users WHERE id = 1 FOR UPDATE")

# ✅ 重试机制
import time

max_retries = 3
for attempt in range(max_retries):
    try:
        cursor.execute("SELECT * FROM users WHERE id = 1 FOR UPDATE")
        break
    except DeadlockException:
        if attempt == max_retries - 1:
            raise
        time.sleep(0.1 * (2 ** attempt))  # 指数退避

2. 避免在循环中执行带锁查询 ​

sql
-- ❌ 性能差
FOR i IN 1..1000 LOOP
    SELECT * FROM users WHERE id = i FOR UPDATE;
    UPDATE users SET ... WHERE id = i;
END LOOP;

-- ✅ 批量处理
SELECT * FROM users WHERE id BETWEEN 1 AND 1000 FOR UPDATE;
UPDATE users SET ... WHERE id BETWEEN 1 AND 1000;

3. 避免大事务持有排他锁 ​

sql
-- ❌ 不推荐
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
INSERT INTO logs ...;  -- 大量日志插入
UPDATE users SET ... WHERE id = 1;
COMMIT;

-- ✅ 拆分事务
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET ... WHERE id = 1;
COMMIT;

BEGIN;
INSERT INTO logs ...;
COMMIT;

🔍 监控与诊断 ​

MySQL 监控 ​

sql
-- 查看排他锁
SELECT 
    lock_id,
    lock_trx_id,
    lock_mode,
    lock_type,
    lock_table,
    lock_data
FROM performance_schema.data_locks
WHERE lock_mode = 'X';

-- 查看锁等待
SELECT 
    requesting_thread_id,
    blocking_thread_id,
    wait_age
FROM performance_schema.data_lock_waits;

-- 查看死锁
SHOW ENGINE INNODB STATUS\G

PostgreSQL 监控 ​

sql
-- 查看排他锁
SELECT 
    l.relation::regclass AS table_name,
    l.mode,
    l.granted,
    a.pid,
    a.query
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE l.mode IN ('ExclusiveLock', 'RowExclusiveLock');

-- 查看锁等待
SELECT * FROM pg_stat_activity 
WHERE wait_event_type = 'Lock';

SQL Server 监控 ​

sql
-- 查看排他锁
SELECT 
    request_session_id,
    OBJECT_NAME(resource_associated_entity_id) AS table_name,
    request_mode,
    request_status
FROM sys.dm_tran_locks
WHERE request_mode IN ('X', 'IX');

-- 查看阻塞
EXEC sp_who2;

🚨 常见问题 ​

Q1: 排他锁和共享锁的区别? ​

答:

特性排他锁 (X)共享锁 (S)
兼容性与任何锁都不兼容与其他 S 锁兼容
用途写操作读操作
并发度低高
死锁风险有无(纯 S 锁)

Q2: 如何避免排他锁导致的性能问题? ​

答:

  1. 缩短事务时间: 快速提交
  2. 减少锁粒度: 使用行锁而非表锁
  3. 批量操作: 减少锁获取次数
  4. 合理索引: 精确定位需要锁定的行
  5. 读写分离: 从库承担读负载

Q3: 排他锁会导致死锁吗?如何预防? ​

答: 会。预防措施:

  1. 固定加锁顺序: 总是按相同顺序获取锁
  2. 缩短事务时间: 快速提交
  3. 设置超时: 避免无限等待
  4. 重试机制: 捕获死锁异常并重试

📚 相关资源 ​

内部链接 ​

外部资源 ​


最后更新: 2026-04-12
维护状态: ✅ 完整

Released under MIT License.