在多用户并发访问数据库的环境中,多个事务可能同时操作相同的数据。如果不加控制,就可能出现脏读、不可重复读、幻读等数据一致性问题。锁机制正是为了解决这一问题而设计的核心手段:当事务要对数据库对象进行操作前,需要先获取相应的锁,从而对该对象获得一定的控制权,在其他事务释放锁之前,不允许其他事务进行可能引发冲突的操作。
锁的周期:锁通常在事务开始时获取,在事务提交或回滚时释放。这意味着长时间未提交的事务会长时间持有锁资源,成为阻塞其他事务的潜在源头。
锁的管理方式:达梦数据库采用了MVCC与封锁机制相结合的策略。MVCC通过维护数据的多个版本,使得读操作可以访问旧版本数据而不被写操作阻塞;封锁机制则用于协调写操作之间的冲突。
达梦数据库使用四种不同的锁模式,分别服务于不同的并发控制需求:
用途:读操作,防止其他事务修改正在访问的对象。
特点:允许多个事务同时并发读取相同资源,但不允许任何事务修改该资源。这种“共享读、互斥写”的特性使其适用于只读场景。
典型场景:执行SELECT查询时,如果隔离级别较高或使用了特定提示,可能加共享锁。
用途:写操作,以独占方式访问对象,防止其他事务读或写。
特点:不允许任何其他事务访问被封锁对象,是最严格的锁模式。通常在修改数据(INSERT、UPDATE、DELETE)或修改对象定义(DDL)时使用。
典型场景:执行UPDATE语句修改某行数据时,会在该行上加排他锁。
用途:在只读访问对象时,在更高粒度(如表级)上声明“此事务将在更细粒度上加共享锁”的意图。
特点:多个事务可以同时在同一个对象上加意向锁,意向锁之间相互兼容。意向锁的主要作用是提高锁检测效率——当事务要在表上加排他锁时,只需检查是否已存在表级意向锁,而无需逐行检查行锁。
用途:在修改对象数据时,在表级声明“此事务将在更细粒度上加排他锁”的意图。
特点:与意向共享锁类似,多个事务可同时在同一对象上加意向排他锁。
锁模式之间的兼容关系决定了并发事务的阻塞行为。下表展示了不同锁模式的兼容性(Y表示相容,N表示不相容):
| 请求锁模式 →<br />已持有锁模式 ↓ | 共享锁(S) | 排他锁(X) | 意向共享锁(IS) | 意向排他锁(IX) |
|---|---|---|---|---|
| 共享锁(S) | Y | N | Y | N |
| 排他锁(X) | N | N | N | N |
| 意向共享锁(IS) | Y | N | Y | Y |
| 意向排他锁(IX) | N | N | Y | Y |
从表中可以看出,排他锁(X)与任何其他锁模式都不相容——一旦某资源被加上了排他锁,其他事务的任何访问都将被阻塞。而意向锁之间以及与共享锁之间存在一定的相容性,这使得意向锁能够有效解决传统锁机制中锁粒度粗、冲突多的问题。
达梦提供了LOCK TABLE语句,允许用户显式地为表加锁:
sql
-- 给表加共享锁
LOCK TABLE test1 IN SHARE MODE;
-- 给表加排他锁
LOCK TABLE test1 IN EXCLUSIVE MODE;
-- 给表加意向共享锁
LOCK TABLE test1 IN INTENT SHARE MODE;
-- 给表加意向排他锁
LOCK TABLE test1 IN INTENT EXCLUSIVE MODE;
此外,达梦还提供了DBMS_LOCK程序包,用于实现更精细的应用级锁控制,适用于秒杀等需要自定义锁逻辑的场景。
达梦数据库支持多种锁粒度,从粗到细包括:
细粒度锁(如行级锁)可以提高并发度,但管理复杂度也更高;粗粒度锁(如表级锁)管理简单,但可能导致更多的锁冲突。达梦通过意向锁机制在两者之间取得了良好的平衡——事务在加行锁的同时,会在表级加意向锁,从而让其他事务在检查表级锁时能够快速判断是否存在行锁冲突,而无需逐行扫描。
在实际运行中,锁机制可能引发三种需要关注的现象:锁等待、阻塞和死锁。理解三者之间的区别与联系,是进行故障诊断的基础。
定义:锁等待是指一个事务在请求某个锁时,由于该锁已被其他事务持有,导致请求事务必须等待。
锁等待本身是正常的并发控制行为——只要等待时间在可接受范围内,且最终能够获取锁,就不会对系统造成实质影响。只有当等待时间过长或形成循环等待时,才会升级为需要干预的问题。
定义:阻塞是指一个事务占用了某资源的锁而未释放,导致其他需要该资源的事务被迫长时间等待。
典型场景:
DDL_WAIT_TIME参数指定的时间(默认10秒)后报错“锁超时”。阻塞的解决策略:
定义:死锁是指两个或多个事务形成等待环,每个事务都在等待其他事务释放锁,结果所有事务都无法继续执行。
死锁的典型场景:
假设有两个表TEST01和TEST02,以及两个并发事务:
| 步骤 | 事务A(会话1) | 事务B(会话2) |
|---|---|---|
| 1 | INSERT INTO test01 VALUES(3,'Wang Wu');<br />(test01上有排他锁) |
INSERT INTO test02 VALUES(3,'Wang Wu');<br />(test02上有排他锁) |
| 2 | INSERT INTO test02 VALUES(3,'Wang Wu');<br />(尝试获取test02锁,被事务B阻塞) |
INSERT INTO test01 VALUES(3,'Wang Wu');<br />(尝试获取test01锁,被事务A阻塞) |
此时,事务A等待事务B释放test02的锁,事务B等待事务A释放test01的锁,形成循环等待——死锁发生。
死锁与阻塞的区别:
达梦的死锁处理策略:
达梦数据库提供了一系列动态性能视图,用于监控和分析锁的状态。掌握这些视图是进行锁问题诊断的前提:
| 视图名称 | 主要用途 | 关键字段 |
|---|---|---|
| V$SESSIONS | 显示所有会话的详细信息 | SESS_ID, SQL_TEXT, TRX_ID, USER_NAME, CLNT_IP, STATE |
| V$TRX | 显示所有活动事务的信息 | ID(事务ID), SESS_ID |
| V$TRXWAIT | 显示事务等待关系 | ID(等待中的事务), WAIT_FOR_ID(被等待的事务) |
| V$LOCK | 显示当前系统中锁的状态 | TRX_ID, LTYPE, LMODE, BLOCKED, TABLE_ID, TID |
| V$DEADLOCK_HISTORY | 显示历史死锁信息 | 死锁发生的时间、涉及的事务等 |
| V$TRX_VIEW | 显示活动事务视图信息 | 用于MVCC可见性判断 |
重要说明:
V$DEADLOCK_HISTORY需要确保dm.ini中的参数ENABLE_MONITOR=1,否则历史死锁信息不会被记录。V$LOCK视图中的LTYPE字段表示锁类型,可能为OBJECT(对象锁)或TID(TID行锁)。V$LOCK中的BLOCKED字段标识该锁是否处于等待状态(1表示等待中,0表示已获取)。达梦社区技术 https://eco.dameng.com
文章
阅读量
获赞
