遇到一个奇怪的现象,select 和 delete 表时正常执行,但 truncate 和 drop 表时会一直运行,也不报错。
一、查了些资料才发现问题的原因,总结如下:
" drop table " 和 " truncate table " 需要申请排它锁 " ACCESS EXCLUSIVE ", 执行这个命令卡住时,说明此时这张表上还有操作正在进行,比如查询等,那么只有等待这个查询操作完成," drop table " 或" truncate table "或者增加字段的 SQL 才能获取这张表上的 " ACCESS EXCLUSIVE " 锁 ,操作才能进行下去。
补充内容:产生锁冲突的操作及锁类型
在数据库操作中,不同的SQL命令会申请不同类型的锁,以确保数据的一致性和完整性。以下是一些常见的操作及其申请的锁类型:
- SELECT :默认情况下,
SELECT
操作会申请共享锁(Share Locks),这意味着多个事务可以同时读取数据,但不能修改。共享锁的类型通常是ACCESS SHARE
。- UPDATE、DELETE :这些操作会申请排它锁(Exclusive Locks),但不是
ACCESS EXCLUSIVE
。它们通常申请ROW EXCLUSIVE
或SHARE UPDATE EXCLUSIVE
锁,这允许事务修改数据,但不允许其他事务同时修改同一行。- INSERT :
INSERT
操作也会申请ROW EXCLUSIVE
锁,以确保新插入的行不会被其他事务同时修改。- ALTER TABLE :
ALTER TABLE
操作,如添加或删除列,会申请ACCESS EXCLUSIVE
锁,因为这些操作需要修改表结构,可能会影响所有行。- TRUNCATE TABLE :
TRUNCATE TABLE
操作会申请ACCESS EXCLUSIVE
锁,因为它需要删除表中的所有行,这是一个重量级操作,需要确保没有其他事务正在访问表。- DROP TABLE :
DROP TABLE
操作同样需要ACCESS EXCLUSIVE
锁,因为它会完全删除表,这是一个不可逆的操作,需要确保没有其他事务正在使用表。- LOCK TABLE :使用
LOCK TABLE
命令时,可以指定不同的锁模式,如ACCESS SHARE
、ROW SHARE
、ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、SHARE
、SHARE ROW EXCLUSIVE
或ACCESS EXCLUSIVE
。当一个事务持有共享锁(如
ACCESS SHARE
)时,其他事务可以读取数据但不能修改。而当一个事务尝试获取ACCESS EXCLUSIVE
锁时,它会等待所有现有的共享锁释放。如果存在未完成的查询或其他操作,它们持有的共享锁会阻止ACCESS EXCLUSIVE
锁的获取,从而导致TRUNCATE TABLE
或DROP TABLE
操作卡住。为了解决这种锁冲突,可以采取以下步骤:
- 确定持有锁的事务的进程ID(
procpid
)。- 使用
pg_cancel_backend
或pg_terminate_backend
函数终止持有锁的事务。这些操作应该谨慎执行,因为终止事务可能会导致数据不一致或其他问题。在执行这些操作之前,最好先评估影响,并确保有适当的数据备份。
二、知道原因后解锁步骤如下:
1.检索出死锁进程的ID。
SELECT * FROM pg_stat_activity WHERE datname='死锁的数据库ID ';
检索出来的字段中,【 wating 】字段,数据为t的那条,就是锁等待的进程。找到对应的【 procpid 】列的值。
注:
数据库中有两种基本的锁:排它锁(Exclusive Locks)和共享锁(Share Locks)。 如果数据对象加上排它锁,则其他的事务不能对它读取和修改。 如果加上共享锁,则该数据库对象可以被其他事务读取,但不能修改。 锁定模式:ACCESS SHARE,ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE,SHARE ROW EXCLUSIVE,EXCLUSIVE,ACCESS EXCLUSIVE。 LOCK TABLE department1 IN ACCESS EXCLUSIVE MODE;
查看锁的会话,确定锁的来源:
select * from pg_stat_activity_global where current_query<>''; select * from oushu_lock_status where pid = 38099 ; select * from oushu_lock_status where relation = 955982; select * from pg_stat_activity_global where procpid=630657; select pg_terminate_backend(27189)
2.将进程杀掉。
SELECT pg_cancel_backend('死锁那条数据的procpid值 ');
结果:运行完后,再次更新这个表,sql 顺利执行。
如果 pg_stat_activity_global 没有记录,则查询 pg_locks 是否有这个对象的锁
select oid,relname from pg_class where relname='table name';
select locktype,pid,relation,mode,granted,* from pg_locks where relation='上面查询出来的oid';
SELECT pg_cancel_backend('进程ID ');
另外,也可以使用 pg_terminate_backend() 函数也可以杀掉进程。