Skip to content

显式锁 (Explicit Lock) ​

📋 概述 ​

显式锁(Explicit Lock)是用户通过 SQL 语句手动指定的锁机制。与隐式锁不同,显式锁需要开发者明确地告诉数据库:何时加锁、加什么类型的锁、锁定哪些资源。

核心特点 ​

✅ 优点:

  • 精细控制: 精确控制锁的时机和范围
  • 灵活性高: 可以根据业务需求定制
  • 性能优化: 可以避免不必要的锁竞争
  • 特殊场景: 适合复杂的并发控制需求

❌ 缺点:

  • 复杂度高: 需要深入理解锁机制
  • 容易出错: 可能忘记释放锁导致死锁
  • 维护成本: 代码复杂度增加
  • 风险较高: 不当使用会严重影响性能

适用场景 ​

  • 临界区保护: 需要独占访问的代码段
  • 复杂事务: 多步骤操作需要保证一致性
  • 高性能要求: 需要精细优化锁行为
  • 特殊业务逻辑: 如秒杀、抢购等高并发场景

🔧 工作原理 ​

基本的显式锁语法 ​

MySQL ​

sql
-- SELECT ... FOR UPDATE(排他锁)
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- SELECT ... LOCK IN SHARE MODE(共享锁)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;

-- LOCK TABLES(表级锁)
LOCK TABLES users WRITE, orders READ;
-- 执行操作
UNLOCK TABLES;

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

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

PostgreSQL ​

sql
-- FOR UPDATE
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- FOR SHARE
SELECT * FROM users WHERE id = 1 FOR SHARE;

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

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

-- SKIP LOCKED
SELECT * FROM users WHERE id IN (1,2,3) FOR UPDATE SKIP LOCKED;

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;

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

-- NOLOCK(不加锁,读未提交)
SELECT * FROM users WITH (NOLOCK);

Oracle ​

sql
-- FOR UPDATE
BEGIN
    SELECT * INTO user_record FROM users WHERE id = 1 FOR UPDATE;
    UPDATE users SET balance = balance - 100 WHERE id = 1;
    COMMIT;
END;

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

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

🎯 最佳实践 ​

✅ 推荐做法 ​

1. 缩短锁持有时间 ​

sql
-- ✅ 推荐:快速完成
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
UPDATE orders SET status = 'processed' WHERE id = 1;
COMMIT;

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

2. 固定加锁顺序 ​

sql
-- ✅ 总是按相同顺序加锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;
SELECT * FROM users WHERE id = 2 FOR UPDATE;

3. 设置超时 ​

sql
-- MySQL
SET innodb_lock_wait_timeout = 10;

-- PostgreSQL
SET lock_timeout = '5s';

-- SQL Server
SET LOCK_TIMEOUT 5000;

4. 实现重试机制 ​

python
import time

def execute_with_explicit_lock(max_retries=3):
    for attempt in range(max_retries):
        try:
            cursor.execute("SELECT * FROM users WHERE id = 1 FOR UPDATE")
            # 执行业务逻辑
            cursor.execute("UPDATE users SET balance = balance - 100 WHERE id = 1")
            connection.commit()
            return
        except DeadlockException:
            connection.rollback()
            if attempt == max_retries - 1:
                raise
            time.sleep(0.1 * (2 ** attempt))

❌ 避免陷阱 ​

1. 避免忘记释放锁 ​

python
# ❌ 危险:异常时未释放锁
cursor.execute("SELECT * FROM users WHERE id = 1 FOR UPDATE")
# 如果这里出错
update_user()  # 异常
connection.commit()  # 不会执行,锁一直持有

# ✅ 正确:使用上下文管理器
with connection.begin():
    cursor.execute("SELECT * FROM users WHERE id = 1 FOR UPDATE")
    update_user()
    # 自动提交或回滚

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
-- ❌ 不推荐
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;

📊 显式锁 vs 隐式锁 ​

特性显式锁隐式锁
控制粒度精细粗粒度
灵活性高低
复杂度高低
风险高低
适用场景特殊需求常规场景
推荐使用比例10%90%

🔍 监控与诊断 ​

MySQL ​

sql
-- 查看显式锁
SELECT 
    lock_id,
    lock_trx_id,
    lock_mode,
    lock_type,
    lock_table,
    lock_data
FROM performance_schema.data_locks;

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

-- InnoDB 状态
SHOW ENGINE INNODB STATUS\G

PostgreSQL ​

sql
-- 查看所有锁
SELECT 
    l.locktype,
    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
ORDER BY a.query_start;

SQL Server ​

sql
-- 查看锁
SELECT 
    request_session_id,
    resource_type,
    request_mode,
    request_status
FROM sys.dm_tran_locks;

-- 查看阻塞
EXEC sp_who2;

🚨 常见问题 ​

Q1: 何时应该使用显式锁? ​

答:

  • 需要精确控制锁时机
  • 复杂的业务逻辑需要多步操作
  • 高并发场景需要优化锁行为
  • 临界区保护

Q2: 显式锁会导致死锁吗? ​

答: 会。预防措施:

  1. 固定加锁顺序
  2. 缩短事务时间
  3. 设置超时
  4. 实现重试机制

Q3: 如何调试显式锁问题? ​

答:

  1. 查看锁信息: 使用数据库提供的锁视图
  2. 分析死锁日志: 查看死锁详细信息
  3. 监控锁等待: 设置告警
  4. 简化事务: 减少锁持有时间

📚 相关资源 ​

内部链接 ​

外部资源 ​


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

Released under MIT License.