显式锁 (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\GPostgreSQL
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: 显式锁会导致死锁吗?
答: 会。预防措施:
- 固定加锁顺序
- 缩短事务时间
- 设置超时
- 实现重试机制
Q3: 如何调试显式锁问题?
答:
- 查看锁信息: 使用数据库提供的锁视图
- 分析死锁日志: 查看死锁详细信息
- 监控锁等待: 设置告警
- 简化事务: 减少锁持有时间
📚 相关资源
内部链接
外部资源
最后更新: 2026-04-12
维护状态: ✅ 完整