页级锁 (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 所在的整个页为何被弃用
- 许可问题: Oracle 收购后不再开源
- 性能局限: 页级锁并发度不如 InnoDB 的行级锁
- 维护成本: MySQL 官方聚焦 InnoDB
- 功能不足: 缺少事务、外键等高级特性
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; -- PostgreSQL2. 避免频繁的锁升级
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: 为什么现代数据库不再使用页级锁?
答:
- 并发需求提升: 互联网应用需要更高的并发度
- 硬件发展: 内存充足,可以承担行锁的开销
- 技术进步: MVCC 等技术使行锁更高效
- 业务场景: OLTP 场景要求精细的锁控制
Q2: 页级锁和行级锁如何选择?
答:
现代数据库几乎都选择行级锁:
| 时代 | 主流锁粒度 | 代表引擎 |
|---|---|---|
| 1990s | 表级锁 | MyISAM |
| 2000s | 页级锁 | BDB |
| 2010s+ | 行级锁 | InnoDB, PostgreSQL |
建议: 除非有特殊需求,否则始终使用行级锁。
Q3: SQL Server 的锁升级是好是坏?
答: 取决于场景:
好处:
- 减少锁内存占用
- 降低锁管理开销
- 避免锁表过大
坏处:
- 降低并发度
- 可能意外阻塞其他事务
建议:
- 默认让 SQL Server 自动管理
- 监控锁升级频率
- 如有问题再调整策略
Q4: 页级锁还有实际应用价值吗?
答:
直接应用: 很少,主要用于:
- 遗留系统维护
- 嵌入式数据库
- 特殊硬件环境
学习价值: 很高,帮助理解:
- 锁粒度的权衡
- 数据库演进历史
- 性能优化思路
📚 相关资源
内部链接
外部资源
最后更新: 2026-04-12
维护状态: ✅ 完整(历史技术参考)