Skip to content

页级锁 (Page-Level Lock) ​

📋 概述 ​

页级锁是数据库锁机制中介于表级锁和行级锁之间的一种锁类型,它锁定的是数据页(Page)。一个数据页通常包含多行记录(例如 8KB-16KB),页级锁的粒度比表级锁细,但比行级锁粗。

核心特点 ​

✅ 优点:

  • 折中方案: 并发度和开销介于表锁和行锁之间
  • 实现相对简单: 比行级锁容易实现
  • 适合特定场景: 对于按页访问的模式效率较高

❌ 缺点:

  • 定位尴尬: 不如行锁灵活,开销比表锁大
  • 现代数据库弃用: 主流引擎已不再使用
  • 仍可能冲突: 同一页内的行操作会互相阻塞
  • 扩展性一般: 不适合超高并发场景

适用场景 ​

  • 历史系统: MySQL BDB 引擎(已废弃)
  • 特定存储引擎: Berkeley DB
  • 学习研究: 理解锁粒度的演进
  • 特殊硬件: 嵌入式或资源受限环境

🔧 工作原理 ​

数据页结构 ​

数据库将数据存储在固定大小的页中:

数据页 (Page) - 通常 8KB 或 16KB
├── 页头 (Page Header)
├── 记录1 (Row 1)
├── 记录2 (Row 2)
├── ...
├── 记录N (Row N)
└── 空闲空间 (Free Space)

当一个事务获取某页的锁后,该页内的所有行都被锁定。

锁的模式 ​

页级锁也分为共享锁和排他锁:

1. 页共享锁 (Page S Lock) ​

  • 多个事务可以同时读取同一页
  • 阻止其他事务获取页排他锁
  • 允许页内行的共享访问
sql
-- 伪代码示例(BDB 引擎)
LOCK PAGE page_id IN SHARE MODE;
SELECT * FROM table WHERE id IN (page_rows);

2. 页排他锁 (Page X Lock) ​

  • 只有一个事务可以锁定某页
  • 允许修改页内的任何行
  • 阻止其他事务读写该页
sql
-- 伪代码示例(BDB 引擎)
LOCK PAGE page_id IN EXCLUSIVE MODE;
UPDATE table SET name = 'test' WHERE id IN (page_rows);

锁定范围 ​

页级锁的影响范围:

页 1 (锁定) → 行 1-100 全部被锁定
页 2 (未锁定) → 行 101-200 可正常访问
页 3 (锁定) → 行 201-300 全部被锁定

问题: 即使只更新一行,也会锁定整页的其他行。


💻 各数据库实现 ​

MySQL BDB 引擎(已废弃) ​

Berkeley DB (BDB) 是 MySQL 早期支持的存储引擎之一,使用页级锁。

历史背景 ​

MySQL 版本支持情况:
- MySQL 3.x - 5.0: 支持 BDB 引擎
- MySQL 5.1+: 移除 BDB 支持
- 原因:Oracle 收购 Sleepycat,许可协议变化

使用示例(历史) ​

sql
-- 创建 BDB 表(旧版本 MySQL)
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100)
) ENGINE=BDB;

-- BDB 自动使用页级锁
UPDATE users SET name = 'test' WHERE id = 1;
-- 锁定 id=1 所在的整个页

为何被弃用 ​

  1. 许可问题: Oracle 收购后不再开源
  2. 性能局限: 页级锁并发度不如 InnoDB 的行级锁
  3. 维护成本: MySQL 官方聚焦 InnoDB
  4. 功能不足: 缺少事务、外键等高级特性

SQL Server(历史实现) ​

SQL Server 早期版本支持页级锁,现代版本主要使用行级锁。

锁升级机制 ​

SQL Server 有锁升级(Lock Escalation)机制:

行锁数量 > 阈值 (默认 5000)
    ↓
升级为页锁
    ↓
继续增加
    ↓
升级为表锁

查看页锁 ​

sql
-- 查看当前的页锁
SELECT 
    request_session_id,
    resource_type,
    resource_database_id,
    resource_associated_entity_id,
    request_mode,
    request_status
FROM sys.dm_tran_locks
WHERE resource_type = 'PAGE';

-- 查看锁升级事件
SELECT 
    session_id,
    event_sequence,
    lock_escalation_desc
FROM sys.dm_tran_lock_escalations;

控制锁升级 ​

sql
-- 禁用锁升级
ALTER TABLE users SET (LOCK_ESCALATION = DISABLE);

-- 强制升级到页锁(而非表锁)
ALTER TABLE users SET (LOCK_ESCALATION = AUTO);

-- 设置升级阈值
ALTER TABLE users SET (LOCK_ESCALATION_THRESHOLD = 1000);

📊 性能影响分析 ​

并发度对比 ​

锁粒度并发度内存开销实现复杂度
表级锁⭐⭐低简单
页级锁⭐⭐⭐中中等
行级锁⭐⭐⭐⭐⭐高复杂

适用场景评估 ​

场景页级锁表现说明
单行查询⭐⭐⭐锁定整页,浪费
批量页操作⭐⭐⭐⭐刚好匹配
全表扫描⭐⭐不如表锁
高并发 OLTP⭐⭐不如行锁
读多写少⭐⭐⭐可接受

性能测试对比 ​

场景:100个并发事务,每个更新1行,分布在10个页上

表级锁:串行执行,耗时 ~50秒
页级锁:10个页并行,耗时 ~5秒
行级锁:100行完全并行,耗时 ~1秒

🎯 最佳实践 ​

✅ 推荐做法 ​

1. 现代系统避免使用页级锁 ​

sql
-- ❌ 不推荐:使用页级锁引擎
CREATE TABLE users (...) ENGINE=BDB;  -- 已废弃

-- ✅ 推荐:使用行级锁引擎
CREATE TABLE users (...) ENGINE=InnoDB;  -- MySQL 默认

2. SQL Server 中监控锁升级 ​

sql
-- 定期检查锁升级事件
SELECT 
    object_name,
    lock_escalation_count,
    last_escalation_time
FROM sys.dm_db_index_operational_stats(DB_ID(), NULL, NULL, NULL);

3. 调整页大小优化性能 ​

sql
-- SQL Server 创建表时指定填充因子
CREATE INDEX idx_users ON users(id) 
WITH (FILLFACTOR = 80);  -- 留出20%空间减少页分裂

❌ 避免陷阱 ​

1. 避免依赖过时的引擎 ​

sql
-- ❌ 危险:BDB 已不再支持
ENGINE=BDB;

-- ✅ 使用现代引擎
ENGINE=InnoDB;     -- MySQL
ENGINE=heap;       -- PostgreSQL

2. 避免频繁的锁升级 ​

sql
-- ❌ 大量行锁触发升级,影响并发
UPDATE users SET status = 'active' WHERE id BETWEEN 1 AND 10000;

-- ✅ 分批处理
UPDATE users SET status = 'active' WHERE id BETWEEN 1 AND 1000;
UPDATE users SET status = 'active' WHERE id BETWEEN 1001 AND 2000;
-- ...

3. 避免忽略页竞争 ​

sql
-- 监控页等待
SELECT 
    page_wait_type,
    waiting_tasks_count,
    wait_time_ms
FROM sys.dm_db_page_info(DB_ID(), file_id, page_id);

🔍 监控与诊断 ​

SQL Server 监控 ​

sql
-- 查看页锁统计
SELECT 
    object_name(object_id) AS table_name,
    index_id,
    page_lock_count,
    page_lock_wait_count,
    page_lock_wait_in_ms
FROM sys.dm_db_index_operational_stats(DB_ID(), NULL, NULL, NULL);

-- 查看锁升级
SELECT * FROM sys.dm_tran_lock_escalations;

-- 实时监控页锁
SELECT 
    l.request_session_id,
    OBJECT_NAME(p.object_id) AS table_name,
    p.page_id,
    l.request_mode,
    l.request_status
FROM sys.dm_tran_locks l
JOIN sys.dm_db_database_page_allocations(DB_ID(), NULL, NULL, NULL, NULL) p
    ON l.resource_associated_entity_id = p.allocated_page_page_id
WHERE l.resource_type = 'PAGE';

MySQL 监控(历史参考) ​

sql
-- BDB 引擎状态(旧版本)
SHOW ENGINE BDB STATUS;

-- 查看页活动
SHOW STATUS LIKE 'Bdb_%';

🚨 常见问题 ​

Q1: 为什么现代数据库不再使用页级锁? ​

答:

  1. 并发需求提升: 互联网应用需要更高的并发度
  2. 硬件发展: 内存充足,可以承担行锁的开销
  3. 技术进步: MVCC 等技术使行锁更高效
  4. 业务场景: OLTP 场景要求精细的锁控制

Q2: 页级锁和行级锁如何选择? ​

答:

现代数据库几乎都选择行级锁:

时代主流锁粒度代表引擎
1990s表级锁MyISAM
2000s页级锁BDB
2010s+行级锁InnoDB, PostgreSQL

建议: 除非有特殊需求,否则始终使用行级锁。


Q3: SQL Server 的锁升级是好是坏? ​

答: 取决于场景:

好处:

  • 减少锁内存占用
  • 降低锁管理开销
  • 避免锁表过大

坏处:

  • 降低并发度
  • 可能意外阻塞其他事务

建议:

  • 默认让 SQL Server 自动管理
  • 监控锁升级频率
  • 如有问题再调整策略

Q4: 页级锁还有实际应用价值吗? ​

答:

直接应用: 很少,主要用于:

  • 遗留系统维护
  • 嵌入式数据库
  • 特殊硬件环境

学习价值: 很高,帮助理解:

  • 锁粒度的权衡
  • 数据库演进历史
  • 性能优化思路

📚 相关资源 ​

内部链接 ​

外部资源 ​


最后更新: 2026-04-12
维护状态: ✅ 完整(历史技术参考)

Released under MIT License.