达梦数据库关于死锁与阻塞
达梦数据库关于死锁与阻塞
在数据库管理中,死锁和阻塞是常见的并发控制问题,它们会影响系统的性能和稳定性。本文将详细解释死锁和阻塞的概念,并通过具体的SQL操作演示如何模拟这些场景,以及如何处理这些问题。
定义
死锁
死锁是指两个事务都在等待对方持有的资源锁,要等待对方释放有的资源锁之后才能继续工作,两者互不想让,坚持到底,都在等待彼此完成才继续工作,就是这样的状态,双方都完成不了,从而陷入死循环。
解决策略:
- 数据库中的机制是当发生死锁时会牺牲其中的一个进程来让其继续执行下去。
- 那种情况是应用程序BUG产生的,需要调整程序的逻辑结构,在对多表进行操作时,尽量按照相同的顺序执行,避免同时锁定两个资源,必须同时锁定两个资源的时候,要保证在任何时候都要按照相同顺序来锁定资源。
阻塞
阻塞是指第一个事务占有资源没有释放,而第二个事务需要获取这个资源,如果第一个事务没有提交或者回滚,第二个事务会一直等待下去,直到第一个事务释放该资源为止。
解决策略:
- 被阻塞的事务会一直挂起、直到持有锁的事务放弃锁定的资源为止(提交/回滚);
- 程序应用bug产生,需要调整程序的逻辑结构,尽量形成短事务,快速提交,避免阻塞时间过长,不要将其他无关操作放到容易引起阻塞的模块。
模拟测试
事务锁
创建测试表 TEST01:
create table test01(id int primary key, name varchar(20));
执行查询操作:
select * from test01;
此时,查询 v$lock 该表的锁,可以看到读操作时有IS锁(意向共享锁)。
获取表的ID:
select
o.name,
o.id
from sysobjects o
where o.name = 'TEST01';
使用ID查询该表的锁:
select
t.TRX_ID,
t.LTYPE,
t.LMODE,
t.BLOCKED,
t.TABLE_ID,
t.TID
from v$lock t
where t.table_id = '1029';
打开一个新会话(会话一),在 TEST01 表中插入一条测试数据(未提交):
insert into test01(id, name) values(1, 'zhang san');
此时查看 v$lock,新增表对象的IS(意向共享锁)和IX(意向排他锁):
锁等待
INSERT锁等待
当多个事务同时试图向有主键或UNIQUE约束的表中插入相同的数据时,前一个事务未提交,后面的事务将被阻塞,直到前一个事务提交或回滚。
再开一个会话(会话二),再插入与会话一相同的数据,因为该表有主键,会话一未提交,此时可以看到会话被阻塞:
insert into test01(id, name) values(1, 'zhang san');
查询 v$lock 可以看到该对象新增了TID共享锁(S锁),并且该锁 BLOCKED=1,该锁被事务 42286(会话一)事务阻塞:
此时,如果我们提交会话一插入的数据,再次查询 v$lock,可以看到提交之后相关的事务锁已经释放
并且会话二抛出异常
UPDATE和DELETE锁等待
当UPDATE和DELETE修改的记录已经被另一个事务修改过(未提交),将会发生阻塞,直到未提交的造成阻塞的事务提交或回滚。
再开一个会话(会话三),更新 id 为 1 的记录:
update test01 set name = 'li si' where id = '1';
再开一个会话(会话四),删除 id 为 1 的记录:
delete test01 where id = '1';
此时查看 v$lock 或 v$trxwait,可以看到会话被阻塞:
select * from v$trxwait;
处理锁等待
查询处于等待的锁(或查询v$trxwait可以查看阻塞事务):
select
TRX_ID,
LTYPE,
LMODE,
BLOCKED,
TABLE_ID,
TID
from v$lock
where blocked = '1';
根据查询得到阻塞的事务为 42296 找到会话ID:
select
s.SESS_ID,
s.SQL_TEXT,
s.TRX_ID,
s.THRD_ID
from v$sessions s
where s.TRX_ID = '42296';
使用 sp_close_session 系统过程即可杀死会话,释放阻塞
- 也可以使用操作系统命令 kill -9 线程ID 杀死线程,上面查询的 THRD_ID 为线程ID
call sp_close_session(139860853285584);
再次查询 v$lock 可以看到会话阻塞问题解决
死锁
创建第二张测试表 TEST02:
create table test02(id int primary key, name varchar(20));
会话一:对 TEST01 插入一条数据(此时 TEST01 表上有了排他锁):
insert into test01(id, name) values(3, 'Wang Wu');
会话二:对 TEST02 插入和 TEST01 相同数据(此时 TEST02 表上有了排他锁)
insert into test02(id, name) values(3, 'Wang Wu');
在会话一上往 TEST02 表插入相同数据,因为有主键,产生锁等待:
insert into test02(id, name) values(3, 'Wang Wu');
在会话二上往 TEST01 表插入相同数据,此时产生死锁,可以看到DM数据库自动检测到死锁的产生,并报出错误:
insert into test01(id, name) values(3, 'Wang Wu');
死锁的本质也是锁等待,所以解决锁等待的问题,就解决了死锁的问题。
DDL锁超时
当我们对某张表执行DDL操作时,若当前表上有排他锁(未提交的DML事务),此时数据库会根据锁的等待时间抛出锁超时的异常。
DM数据库的DDL锁超时时间是由参数 DDL_WAIT_TIME 指定,默认十秒,可根据实际需要修改。
select * from v$dm_ini t where t.PARA_NAME = 'DDL_WAIT_TIME';
新增会话一,在 TEST01 表中插入数据(未提交),产生一个排他锁:
insert into test01(id, name) values(5, 'Zhao Liu');
新增会话二,执行DDL语句(对 TEST01 新增字段),此时会话处理等待状态:
alter table test01 add column age varchar(50);
可以看到事务处于阻塞等待:
select
t.TRX_ID,
t.LTYPE,
t.LMODE,
t.BLOCKED,
t.TABLE_ID,
t.TID
from v$lock t
where t.table_id = '1029';
大概十多秒,会话二抛出锁超时的异常
锁超时的本质原因是锁等待,解决锁等待的问题,就解决了锁超时的问题。