Skip to content

GaussDB 锁机制

GaussDB 采用多种锁机制来实现并发控制,确保多个事务并发执行时的数据一致性和完整性。

锁的基本概念

什么是锁

锁是数据库用于控制并发访问共享资源的机制,通过锁可以防止多个事务同时修改同一资源,避免数据不一致。

锁的作用

  • 保证数据一致性
  • 防止丢失更新
  • 防止脏读
  • 实现事务隔离

锁的粒度

锁粒度指锁保护的资源范围,GaussDB 支持多种锁粒度:

行级锁

  • 最细粒度的锁
  • 只锁定被访问的行
  • 并发性能最高
  • 适合高并发读写场景

页级锁

  • 锁定整个数据页
  • 粒度中等
  • 适合批量数据操作
  • 减少锁管理开销

表级锁

  • 锁定整个表
  • 粒度最粗
  • 并发性能最低
  • 适合全表操作

数据库级锁

  • 锁定整个数据库
  • 仅用于特定管理操作
  • 如数据库备份、恢复等

锁的类型

GaussDB 支持多种锁类型,用于不同的访问模式:

共享锁(S 锁)

  • 用于读取操作
  • 允许其他事务读取同一资源
  • 不允许其他事务修改同一资源
  • 多个事务可以同时持有共享锁

排他锁(X 锁)

  • 用于修改操作
  • 不允许其他事务读取或修改同一资源
  • 只能有一个事务持有排他锁

意向锁

  • 表示事务对某个资源的意向访问模式
  • 分为意向共享锁(IS)和意向排他锁(IX)
  • 用于提高锁检查效率
  • 防止死锁

更新锁(U 锁)

  • 用于更新操作的准备阶段
  • 允许其他事务读取,但不允许修改
  • 同一资源只能有一个更新锁
  • 可以升级为排他锁

意向更新锁(IU 锁)

  • 表示事务对某个资源有意向获取更新锁
  • 用于表级锁
  • 提高锁检查效率

锁的兼容性

不同类型的锁之间存在兼容性规则:

请求锁类型S 锁X 锁IS 锁IX 锁U 锁IU 锁
S 锁兼容冲突兼容兼容冲突兼容
X 锁冲突冲突冲突冲突冲突冲突
IS 锁兼容冲突兼容兼容兼容兼容
IX 锁兼容冲突兼容兼容兼容兼容
U 锁冲突冲突兼容兼容冲突兼容
IU 锁兼容冲突兼容兼容兼容兼容

锁机制实现

锁管理器

锁管理器是 GaussDB 中负责锁管理的核心组件:

主要功能

  • 锁的请求、授予和释放
  • 锁兼容性检查
  • 锁等待队列管理
  • 死锁检测和处理
  • 锁统计信息收集

锁存储结构

GaussDB 使用哈希表存储锁信息:

  • 锁表:存储所有锁信息
  • 资源 ID:唯一标识被锁定的资源
  • 锁类型:存储锁的类型
  • 持有事务列表:持有该锁的事务
  • 等待事务列表:等待该锁的事务

锁获取流程

  1. 事务请求锁定资源
  2. 锁管理器检查资源是否已被锁定
  3. 如果资源未被锁定,授予锁并记录锁信息
  4. 如果资源已被锁定,检查锁兼容性
  5. 如果兼容,授予锁并记录锁信息
  6. 如果不兼容,将事务加入等待队列
  7. 当锁释放时,通知等待队列中的事务

锁释放流程

  1. 事务提交或回滚
  2. 释放所有持有的锁
  3. 从锁表中删除锁信息
  4. 唤醒等待该锁的事务
  5. 处理唤醒事务的锁请求

死锁处理

死锁原因

死锁是指两个或多个事务相互等待对方持有的锁,形成循环等待条件:

  • 循环等待:事务 A 等待事务 B 的锁,事务 B 等待事务 A 的锁
  • 互斥条件:资源只能被一个事务占用
  • 不可抢占:锁只能由持有事务主动释放
  • 持有等待:事务持有锁的同时等待其他锁

死锁检测

GaussDB 采用超时检测和死锁图检测两种方式:

超时检测

  • 设置锁等待超时时间
  • 超过超时时间则终止事务
  • 简单但可能误判
  • 配置参数:lock_timeout

死锁图检测

  • 构建等待图,检测循环等待
  • 定期运行死锁检测算法
  • 准确但有性能开销
  • 配置参数:deadlock_timeout

死锁处理

当检测到死锁时,GaussDB 会:

  1. 选择一个事务作为牺牲品
  2. 回滚牺牲品事务
  3. 释放牺牲品持有的所有锁
  4. 唤醒其他等待事务
  5. 记录死锁信息到日志

死锁预防

可以通过以下方式预防死锁:

  • 统一访问顺序
  • 减少事务持有锁的时间
  • 合理设计锁粒度
  • 使用乐观锁
  • 限制事务大小

锁的高级特性

锁升级

锁升级是指将细粒度锁升级为粗粒度锁:

  • 当同一事务锁定的行数量超过阈值时触发
  • 减少锁管理开销
  • 可能降低并发性能
  • 配置参数:lock escalation threshold

锁超时

锁超时是指事务等待锁的时间超过设定阈值:

  • 防止事务无限等待
  • 自动终止超时事务
  • 配置参数:lock_timeout
  • 默认值:无限制

锁监控

GaussDB 提供了多种方式监控锁:

  • pg_locks 视图:显示当前锁信息
  • pg_stat_activity 视图:显示当前活跃事务
  • pg_blocking_pids() 函数:查找阻塞事务
  • pg_cancel_backend() 函数:取消查询
  • pg_terminate_backend() 函数:终止后端进程

锁等待事件

GaussDB 记录锁等待事件:

  • 锁等待开始时间
  • 锁等待结束时间
  • 锁类型
  • 资源信息
  • 等待事务和持有事务

MVCC 与锁的关系

MVCC 简介

MVCC(多版本并发控制)是 GaussDB 用于实现高并发的机制,通过为每个数据行维护多个版本,允许读写操作并发执行。

MVCC 工作原理

  • 每个数据行有多个版本
  • 事务读取数据时,根据隔离级别选择合适的版本
  • 写操作创建新的版本,不阻塞读操作
  • 读操作不阻塞写操作

MVCC 与锁的协同

MVCC 和锁机制协同工作,实现高效的并发控制:

  • MVCC 处理读写并发
  • 锁机制处理写写并发
  • 结合使用提高并发性能
  • 减少锁竞争

MVCC 实现机制

GaussDB MVCC 实现:

  • 每行数据包含 xmin(创建事务 ID)和 xmax(删除事务 ID)
  • 事务可见性规则:只可见 xmin < 当前事务 ID 且 xmax 为空或 > 当前事务 ID 的行
  • 旧版本通过 VACUUM 清理
  • 支持多版本快照

锁的使用场景

读操作

  • 共享锁(S 锁)
  • 允许其他事务读取
  • 不允许其他事务修改
  • 自动获取和释放

写操作

  • 排他锁(X 锁)
  • 不允许其他事务读取或修改
  • 自动获取和释放
  • 包括 INSERT、UPDATE、DELETE 操作

表级操作

  • 表级锁
  • 用于 ALTER TABLE、TRUNCATE 等操作
  • 阻塞所有表级操作
  • 自动获取和释放

显式锁

通过 LOCK 语句显式获取锁:

sql
LOCK TABLE table_name IN mode;

支持的锁模式

  • ACCESS SHARE
  • ROW SHARE
  • ROW EXCLUSIVE
  • SHARE UPDATE EXCLUSIVE
  • SHARE
  • SHARE ROW EXCLUSIVE
  • EXCLUSIVE
  • ACCESS EXCLUSIVE

锁的最佳实践

应用开发最佳实践

合理设计事务

  • 保持事务简短
  • 避免长事务
  • 明确事务边界
  • 减少锁持有时间

优化查询语句

  • 使用索引,减少锁范围
  • 避免全表扫描
  • 优化 WHERE 子句
  • 使用 LIMIT 限制结果集

合理使用锁

  • 避免不必要的锁
  • 使用合适的锁粒度
  • 考虑使用乐观锁
  • 避免锁升级

异常处理

  • 捕获锁等待异常
  • 设置合理的锁超时
  • 实现重试机制

数据库配置最佳实践

调整锁相关参数

  • max_locks_per_transaction:每个事务的最大锁数量
  • deadlock_timeout:死锁检测超时
  • lock_timeout:锁等待超时
  • idle_in_transaction_session_timeout:空闲事务超时

监控锁性能

  • 监控活跃事务数量
  • 监控锁等待时间
  • 监控死锁发生次数
  • 监控长事务

锁的性能优化

减少锁竞争

  • 提高并发度
  • 优化业务逻辑
  • 分离读写操作
  • 使用读写分离

优化锁粒度

  • 优先使用行级锁
  • 避免表级锁
  • 合理设计索引
  • 分区表设计

使用乐观锁

  • 适合读多写少场景
  • 通过版本号或时间戳实现
  • 减少锁竞争
  • 提高并发性能

锁的监控与管理

监控视图

GaussDB 提供了多个系统视图用于监控锁:

pg_locks

显示当前所有锁信息:

  • locktype:锁类型(行、页、表等)
  • database:数据库 OID
  • relation:表 OID
  • page:页号
  • tuple:行号
  • virtualxid:虚拟事务 ID
  • transactionid:事务 ID
  • classid:系统类 OID
  • objid:对象 OID
  • objsubid:对象子 ID
  • virtualtransaction:虚拟事务 ID
  • pid:进程 ID
  • mode:锁模式
  • granted:是否已授予
  • fastpath:是否使用快速路径

pg_stat_activity

显示当前活跃事务:

  • datid:数据库 OID
  • datname:数据库名称
  • pid:进程 ID
  • usesysid:用户 OID
  • usename:用户名
  • application_name:应用名称
  • client_addr:客户端地址
  • client_hostname:客户端主机名
  • client_port:客户端端口
  • backend_start:后端启动时间
  • xact_start:事务开始时间
  • query_start:查询开始时间
  • state_change:状态变化时间
  • wait_event_type:等待事件类型
  • wait_event:等待事件
  • state:后端状态
  • backend_xid:后端事务 ID
  • backend_xmin:后端 xmin
  • query:当前查询
  • backend_type:后端类型

管理工具

内置工具

  • pg_terminate_backend(pid):终止指定后端进程
  • pg_cancel_backend(pid):取消指定后端的当前查询
  • pg_lock_status():显示锁状态
  • pg_blocking_pids(pid):查找阻塞指定进程的进程 ID

第三方工具

  • Prometheus + Grafana:监控锁性能
  • pg_stat_monitor:增强的统计监控
  • pgbadger:日志分析工具
  • pgAdmin:图形化管理工具

常见问题(FAQ)

Q1: 如何查看当前锁信息?

A1: 可以通过查询 pg_locks 视图查看当前锁信息:

sql
SELECT * FROM pg_locks WHERE granted = false;

Q2: 如何查看阻塞事务?

A2: 可以通过以下查询查看阻塞事务:

sql
SELECT blocked_locks.pid     AS blocked_pid,
       blocked_activity.usename  AS blocked_user,
       blocking_locks.pid     AS blocking_pid,
       blocking_activity.usename AS blocking_user,
       blocked_activity.query    AS blocked_query,
       blocking_activity.query   AS blocking_query
FROM  pg_catalog.pg_locks         blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity  ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks         blocking_locks
    ON blocking_locks.locktype = blocked_locks.locktype
    AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
    AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
    AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
    AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
    AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
    AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
    AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
    AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
    AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
    AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;

Q3: 如何设置锁超时?

A3: 可以通过设置 lock_timeout 参数设置锁超时:

sql
SET lock_timeout = '5s';

Q4: 如何避免死锁?

A4: 避免死锁的方法:

  • 统一访问顺序
  • 减少事务持有锁的时间
  • 合理设计锁粒度
  • 使用乐观锁
  • 限制事务大小

Q5: 什么是锁升级?如何避免?

A5: 锁升级是指将细粒度锁升级为粗粒度锁,避免方法:

  • 合理设计索引
  • 减少单事务锁定的行数
  • 调整 lock escalation threshold 参数
  • 优化业务逻辑

Q6: 行级锁和表级锁有什么区别?

A6: 行级锁和表级锁的区别:

  • 行级锁:只锁定被访问的行,并发性能高
  • 表级锁:锁定整个表,并发性能低
  • 行级锁适合高并发读写场景
  • 表级锁适合全表操作

Q7: 什么是 MVCC?它与锁有什么关系?

A7: MVCC(多版本并发控制)是一种并发控制机制,通过为每个数据行维护多个版本,允许读写操作并发执行。MVCC 处理读写并发,锁机制处理写写并发,两者协同工作提高系统并发性能。

Q8: 如何优化锁性能?

A8: 优化锁性能的方法:

  • 减少锁竞争
  • 优化锁粒度
  • 使用乐观锁
  • 优化查询语句
  • 合理设计事务

Q9: 什么是意向锁?它的作用是什么?

A9: 意向锁表示事务对某个资源的意向访问模式,分为意向共享锁(IS)和意向排他锁(IX)。意向锁的作用是提高锁检查效率,防止死锁。

Q10: 如何显式获取锁?

A10: 可以使用 LOCK 语句显式获取锁:

sql
LOCK TABLE table_name IN mode;

支持的锁模式包括:ACCESS SHARE、ROW SHARE、ROW EXCLUSIVE、SHARE UPDATE EXCLUSIVE、SHARE、SHARE ROW EXCLUSIVE、EXCLUSIVE、ACCESS EXCLUSIVE。