暂无图片
暂无图片
4
暂无图片
暂无图片
暂无图片

Oracle中的锁与阻塞

原创 大柏树 2024-10-31
1089

一.官方文档

官方文档对于锁的描述在Database Concepts第三部分Part III Oracle Transaction Management中。
链接

二.锁

1.为什么要有锁 ?

在单个用户的数据库中,不用担心其他用户会修改数据,但是在多用户环境下,多个用户发起的请求中可能会修改同一个数据。 数据库必须保证 用户可以并发访问数据以及保证每个用户都能看到一致的数据。
Oracle数据库使用多版本控制和各种类型的锁和事务来维护数据的一致性。

2.锁类型

A lock is a mechanism that prevents destructive interactions.

Oracle的锁如果按照类型来分,可以分为排它锁(Exclusive)与共享锁(share),或者它们的组合锁。
排它锁(X锁):如果一个对象上被加了X锁,在这个锁被采用commit或rollback释放之前,该对象上不能施加任何其他类型的锁。
共享锁(S锁):如果一个对象被加上了S锁,该对象上还可以再加其他类型的S锁,但是,在该锁释放之前,该对象不能被加其他任何类型的X锁。

Oracle的锁如果按照锁定的对象来分的话,可以分为DML锁、DDL锁与内存锁,内存锁也叫闩(latch)。

3.DML锁

A DML lock, also called a data lock, guarantees the integrity of data accessed concurrently by multiple users.

For example, a DML lock prevents two customers from buying the last copy of a book available from an online bookseller. DML locks prevent destructive interference of simultaneous conflicting DML or DDL operations.

DML statements automatically acquire the following types of locks:

Row Locks (TX)
Table Locks (TM)

DML锁,是对表数据做修改的时候,为了并发地完成一些操作而产生的锁,又可分为 TX锁和TM锁。

3.1.TX锁

TX锁,即事务锁,当一个事务进行数据更新,如update、insert、delete、merger或者select … for update的时候,会对所操作的行产生一个TX锁,直到这个事务被commit或rollback。
Oracle的TX锁是行级别的,但是可以一次性锁定多行,行锁的标志被记录在数据块上。

3.2.TM锁

可以是手工发出来的lock命令,或者是DML或select … for update的时候,加在表对象上的锁,可以防止其他对象对表结构加X类型的锁,如表结构变更、truncate、drop等。
如果表上面存在没有提交的事务,别的会话视图执行DDL语句时就会导致一个错误。
TM锁可以分为如下几个级别,分别对应v$lock中的LMODE字段:
2(RS,row share)
3 (RX,row exclusive)
4 (S,share)
5 (SRX,share row exclusive)
6 (X,exclusive)
DML操作表数据的时候,是先加TM锁,如果能加上,再加TX锁,如果DML与for update加在表上面的锁都是RX锁,再在行上面加TX锁。
因为TX锁上可以再加RX锁,所以,一个表可以同时被多个会话DML操作,只要操作的对象不是同行,TX锁没有冲突就行。
示例如下:

SQL> --session 1 SQL> select * from scott.test1 where id=1 for update; ID ---------- 1 SQL> SQL> select type,lmode from v$lock where sid = (select sid from v$mystat where rownum <=1); TY LMODE -- ---------- TX 6 TM 3 SQL> SQL> --session 2 SQL> SQL> SQL> drop table scott.test1; drop table scott.test1 * ERROR at line 1: ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired SQL> SQL> --session 3 SQL> select * from scott.test1; ID ---------- 1 SQL> update scott.test1 set id =2 where id=1; --可以看到,session3开始等待。 --我们查锁等待,可以看到,产生了TX锁 select a.inst_id, a.process, a.sid, a.serial#, a.sql_id, a.event, a.status, a.program, a.machine, connect_by_isleaf as isleaf, sys_connect_by_path(a.SID || '@' || a.inst_id,' <- ') tree, level as tree_level from gv$session a start with a.blocking_session is not null connect by (a.sid || '@' || a.inst_id) = prior (a.blocking_session || '@' || a.blocking_instance);

image.png
在一个会话中,TX锁一般只有一个,因为只有一个事务,TX锁就是对应的事务锁,而TM锁可以有多个,因为在一个会话中,可能更新多个表。

SQL> update scott.test1 set id=2 where id=1; 1 row updated. SQL> update scott.test2 set id=3 where id=1; 1 row updated. SQL> select sid,id1,id2,type,lmode,request from v$lock where sid=(select sid from v$mystat where rownum <=1); SID ID1 ID2 TY LMODE REQUEST ---------- ---------- ---------- -- ---------- ---------- 151 73096 0 TM 3 0 151 134 121909014 AE 4 0 151 73383 0 TM 3 0 151 458776 815 TX 6 0 SQL>

我们可以看到两个DML语句,产生了两个类别为3的RX级别的TM表级锁,以及一个TX行级锁,这个行级锁其实分布在两个表的不同数据块中,仅当另外的用户想再对这两行加锁的时候,才可能引发冲突。
v$lock中的ID1,在表的TM锁中,表示对象的object_id,根据这个id,可以查询dba_objects视图,获得锁定的对象。

SQL> select object_name from dba_objects where object_id='73096'; OBJECT_NAME ------------------------------------------------------------------- TEST1 SQL> select object_name from dba_objects where object_id='73383'; OBJECT_NAME ------------------------------------------------------------------- TEST2 SQL>

4.DDL锁

Oracle的DDL语句,类似以下的代码:

begin commit; DDL语句 commit; exception; when others then rollback; end;

因为DDL总是提交,所以,操作DDL的时候,需要注意是否不小心提交了本会话的事务。

--session1 SQL> select * from scott.test3; ID ---------- 1 2 3 SQL> SQL> delete from scott.test3 where id=1; 1 row deleted. SQL> SQL> --session 2 SQL> delete from scott.test3; --因为都涉及id=1的记录,可以看到session2处于等待状态。如果在session1执行truncate table的ddl操作,可以看到,ddl失败了。 SQL> truncate table scott.test3; truncate table scott.test3 * ERROR at line 1: ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired SQL> --session3 SQL> select * from scott.test3; ID ---------- 2 3 SQL> --可以看到表只剩两条记录了,session1的dml操作被ddl提交了。 SQL> --session 2 SQL> delete from scott.test3; 2 rows deleted. SQL> --session2的delete也执行成功了。

DDL的锁有好几种类型,但是主要有两类:

  • X类型的DDL,排它类型的DDL,多发生在truncate、drop、alter table drop/add/modify等绝大部分DDL上面。这样的语句也会在操作期间给表加一个X类型的TM锁。
  • S类型的DDL,如online操作,会在表上加一个RS类型的TM锁,因为RS类型的锁是可以再加RX锁的,所以,online操作不阻塞DML语句。

5.内存锁

内存锁,latch,是一个运行在内存中,保护内存结构的轻量级锁。因为内存的速度很快,所以,或者这个内存锁的速度也很快。
简单来说,latch是为了保证内存中的数据不被破坏而存在的。
虽然latch的速度快,但是latch其实也是很珍贵的资源,如果处理的不好,将耗费大量的CPU来管理内存锁。比较典型的就是硬解析,大量耗费latch的同时,耗费大量的CPU时间。所以,在OLTP环境下,一定要使用绑定变量。

三.阻塞

1.TX阻塞

上面了解了TX锁的原理,那么对于TX阻塞就很好理解了。
比如:TX阻塞,一个会话对一行加了TX锁,另外一个会话要再对这个行加TX锁,就加不上了,于是就产生了阻塞,这里的等待是行级别上的等待。
又比如:一个表上面因为创建索引,被加了S锁,而创建索引的时间可能很长,需要10分钟,那么,所有试图对这个表做DML或者for update的语句都会等待,因为它们加不上RX的TM锁,更不用说加TX锁了,所以,这里的等待是表级别上的等待。

行级别的等待与TX锁,都不太耗费资源。事务记录在数据块的事务槽内,一个块的事务个数,可以init trans和max trans来决定、当事务的槽位(IIL)是足够的,就可以在一个块内存在很多个事务,当另外的事务请求到网样的行,只要检查块的事务槽,就知道这个行被锁定了。

看一个行级别等待的例子:

--session 1 SQL> select sid from v$mystat where rownum <=1; SID ---------- 151 SQL> select * from scott.test3 where id=1 for update; ID ---------- 1 SQL> --session 2 SQL> select sid from v$mystat where rownum <=1; SID ---------- 33 SQL> update scott.test3 set id=2 where id=1; --这里被阻塞了 --session3 SQL> select sid,type,lmode,request,block from v$lock where sid in (151,33); SID TY LMODE REQUEST BLOCK ---------- -- ---------- ---------- ---------- 33 AE 4 0 0 151 AE 4 0 0 33 TX 0 6 0 151 TX 6 0 1 151 TM 3 0 0 33 TM 3 0 0 6 rows selected. SQL> --block 为 1 说明 它阻塞了别人 --可以看到session 2,也就是sid 为33的会话,加TX锁的时候,request为6 SQL> set line 200 SQL> col object_name format a20 SQL> col user_name format a20; SQL> select /*+ rule */ lpad('--', decode(b.block,1,0,4)) || s.username user_name,b.type,o.owner || ',' || o.object_name object_name, s.sid,s.serial#,decode(b.REQUEST,0,'BLOCKED','WAITING') status from dba_objects o, v$session s, v$lock v, v$lock b WHERE v. 2 3 4 ID1 = o.object_id AND v.SID = s.sid and v.SID = b.SID and (b.BLOCK = 1 Or b.REQUEST > 0) and V.TYPE = 'TM' order by b.ID2, v.ID1, User_name desc; 5 6 7 8 9 USER_NAME TY OBJECT_NAME SID SERIAL# STATUS -------------------- -- -------------------- ---------- ---------- SYS TX SCOTT,TEST3 151 19183 BLOCKED --SYS TX SCOTT,TEST3 33 62413 WAITING SQL> --可以看到,发生了TX类型的阻塞,sid 151阻塞了sid 33,sid 151是blocked状态,sid 33处于waiting状态。

2.TM阻塞

看一个TM的阻塞示例
这里用lock table table_name in share mode来模拟表创建索引,因为他们与等待TM锁的级别一样。

SQL> --session 1 SQL> select sid from v$mystat where rownum <=1; SID ---------- 151 SQL> lock table scott.test3 in share mode; Table(s) Locked. SQL> SQL> select sid from v$mystat where rownum <=1; SID ---------- 33 SQL> --session 2 SQL> select sid from v$mystat where rownum <=1; SID ---------- 33 SQL> update scott.test3 set id=2 where id=1; --被阻塞 --session 3 set line 200 col object_name format a20; col user_name format a20; select /*+ rule */ lpad('--', decode(b.block,1,0,4)) || s.username user_name,b.type,o.owner || ',' || o.object_name object_name, s.sid,s.serial#,decode(b.REQUEST,0,'BLOCKED','WAITING') status from dba_objects o, v$session s, v$lock v, v$lock b WHERE v.ID1 = o.object_id AND v.SID = s.sid and v.SID = b.SID and (b.BLOCK = 1 Or b.REQUEST > 0) and V.TYPE = 'TM' order by b.ID2, v.ID1, User_name desc;

image.png

3.latch阻塞

因为Oracle特有的锁机制,TX与TM阻塞不会阻塞select语句,因为select语句并不在表上加任何锁,所以,也不会被阻塞。而且,根据Oracle特有的一致性读规则,select将保证获得一致性的数据。
而latch却可以阻塞select,如library cache lock等待。
理论上,如果select时需要获取的Latch资源无法获取到,就会等待。
所以,在业务高峰期尽量少的执行DDL。

四.总结

要尽量避免锁,阻塞的发生。所以在OLTP环境下,一定要:

  • 尽量减少事务的大小。
  • 及时提交当前事务,防止引起阻塞。
  • 尽量避免跨数据库的分布式事务,分布式事务因为环境的复杂性,更容易导致阻塞。
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论