Oracle关于TX锁的一个有趣的问题

时间:2022-09-24 09:20:32

前阵子有一个网友在群里问了一个关于Oracle数据库的TX锁问题,问题原文如下:

请教一个问题: 两个会话执行不同的delete语句,结果都是删除同一个行。先执行的会话里where条件不加索引走全表扫描,表很大,执行很慢;后执行的用where条件直接用rowid进行delete。 Oracle的什么机制使第二个会话执行后一直是等待第一个会话结束的呢。

那么我们先动手实验一下,来看看这个问题吧,首先,我们需要一个数据量较大的表(数据量大,全表扫描时间长,方便构造实验效果), 这里实验测试的表为INV_TEST,该表在字段FINAL_GARMENT_FACTORY_CD上没有索引。因为我们要构造一个SQL走全表扫描去删除数据。我们更新了两条记录,设置字段FINAL_GARMENT_FACTORY_CD ='KLB'。 如下所示:

SQL> SELECT  ROWID, T.FINAL_GARMENT_FACTORY_CD FROM TEST.INV_TEST T WHERE ROWNUM <=10;

 

ROWID              FINAL_GARM

------------------ ----------

AAC1coABNAAALEKAAA KLB

AAC1coABNAAALEKAAB GEG

AAC1coABNAAALEKAAC GEG

AAC1coABNAAALEKAAD GEG

AAC1coABNAAALEKAAE GEG

AAC1coABNAAALEKAAF KLB

AAC1coABNAAALEKAAG GEG

AAC1coABNAAALEKAAH GEG

AAC1coABNAAALEKAAI GEG

AAC1coABNAAALEKAAJ GEG

首先,在会话1(SID=925)里面执行下面SQL语句,删除FINAL_GARMENT_FACTORY_CD ='KLB'的两条记录

SQL> SELECT USERENV('SID') FROM DUAL;

 

USERENV('SID')

--------------

           925

 

SQL> DELETE FROM TEST.INV_TEST WHERE FINAL_GARMENT_FACTORY_CD ='KLB';

在会话1(SID=925)执行后,我们在会话2(SID=197)里面执行一个DELETE语句(删除ROWID ='AAC1coABNAAALEKAAA'的记录),其实就是删除第一条FINAL_GARMENT_FACTORY_CD ='KLB'的记录。不过我们使用的是ROWID这个条件。

 

SQL> SELECT USERENV('SID') FROM DUAL;                                     

 

USERENV('SID')

--------------

           917

 

SQL> DELETE FROM TEST.INV_TEST WHERE ROWID ='AAC1coABNAAALEKAAA';

此时,在会话3,我们使用下面SQL语句查询,就会发现会话2(SID=917)被会话1(SID=925)阻塞了。

SQL> COLUMN blockeduser FORMAT a30 

SQL> SET linesize 480

SQL> BREAK ON BlockingInst SKIP 1 ON BlockingSid skip 1 ON BlockingSerial SKIP 1 

SQL> SELECT DISTINCT s1.INST_ID         BlockingInst, 

  2                  s1.SID             BlockingSid, 

  3                  s1.SERIAL#         BlockingSerial, 

  4                  s2.INST_ID         BlockedInst, 

  5                  s2.SID             BlockedSid, 

  6                  s2.USERNAME        BlockedUser, 

  7                  s2.SECONDS_IN_WAIT BlockedWaitTime 

  8  FROM   gv$session s1, 

  9         gv$lock l1, 

 10         gv$session s2, 

 11         gv$lock l2 

 12  WHERE  s1.INST_ID = l1.INST_ID 

 13         AND l1.BLOCK IN ( 1, 2 ) 

 14         AND l2.REQUEST != 0 

 15         AND l1.SID = s1.SID 

 16         AND l1.ID1 = l2.ID1 

 17         AND l1.ID2 = l2.ID2 

 18         AND s2.SID = l2.SID 

 19         AND s2.INST_ID = l2.INST_ID 

 20  ORDER  BY 1, 

 21            2, 

 22            3 

 23  / 

 

BLOCKINGINST BLOCKINGSID BLOCKINGSERIAL BLOCKEDINST BLOCKEDSID BLOCKEDUSER  BLOCKEDWAITTIME

------------ ----------- -------------- ----------- ---------- ------------ ---------------

           1         925          11600           1        917 TEST         30

SQL> COL SID  FOR 999999;

SQL> COL USERNAME FOR A12;

SQL> COL MACHINE FOR A40;

SQL> COL TYPE FOR A10;

SQL> COL OBJECT_NAME FOR A32;

SQL> COL LMODE FOR A16;

SQL> COL REQUEST FOR A12;

SQL> COL BLOCK FOR 999999;

SQL> SELECT S.SID                             SID, 

  2         S.USERNAME                        USERNAME, 

  3         S.MACHINE                         MACHINE, 

  4         L.TYPE                            TYPE, 

  5         O.OBJECT_NAME                     OBJECT_NAME, 

  6         DECODE(L.LMODE, 0, 'None', 

  7                         1, 'Null', 

  8                         2, 'Row Share', 

  9                         3, 'Row Exlusive', 

 10                         4, 'Share', 

 11                         5, 'Sh/Row Exlusive', 

 12                         6, 'Exclusive')   LMODE, 

 13         DECODE(L.REQUEST, 0, 'None', 

 14                           1, 'Null', 

 15                           2, 'Row Share', 

 16                           3, 'Row Exlusive', 

 17                           4, 'Share', 

 18                           5, 'Sh/Row Exlusive', 

 19                           6, 'Exclusive') REQUEST, 

 20         L.BLOCK                           BLOCK 

 21  FROM   V$LOCK L, 

 22         V$SESSION S, 

 23         DBA_OBJECTS O 

 24  WHERE  L.SID = S.SID 

 25         AND USERNAME != 'SYSTEM' 

 26         AND O.OBJECT_ID(+) = L.ID1; 

 

    SID USERNAME     MACHINE                TYPE       OBJECT_NAME      LMODE            REQUEST   BLOCK

------- ------------ ------------------ ---------- ---------------- ---------------- ------------ -------

    917 TEST    DB-Server.localdomain      TM         INV_TEST         Row Exlusive     None          0

    925 TEST    DB-Server.localdomain      TM         INV_TEST         Row Exlusive     None          0

    925 TEST    DB-Server.localdomain      TX                          Exclusive        None          1

    917 TEST    DB-Server.localdomain      TX                          None             Exclusive     0

使用下面脚本,我们知道,会话197在ROW_ID=AAC1coABNAAALEKAAA 这条记录上等待获取TX锁,从而导致他被阻塞了。

COL object_name FOR A32;

COL row_id FOR A32;

SELECT

     s.p1raw,

     o.owner,

     o.object_name,

     dbms_rowid.rowid_create(1,o.data_object_id,f.relative_fno,s.row_wait_block#,s.row_wait_row#) row_id

 FROM

     v$session s

     JOIN dba_objects o ON s.row_wait_obj# = o.object_id

     JOIN dba_segments m ON o.owner = m.owner

                            AND o.object_name = m.segment_name

     JOIN dba_data_files f ON s.row_wait_file# = f.file_id

                              AND m.tablespace_name = f.tablespace_name

 WHERE

     s.event LIKE 'enq: TX%'

Oracle关于TX锁的一个有趣的问题

其实到这里就可以回答之前网友的问题了。 其实很简单,就是ORACLE数据库的锁机制实现的。我们知道TX锁称为事务锁或行级锁。当Oracle执行DML语句时,系统自动在所要操作的表上申请TM类型的锁。当TM锁获得后,系统再自动申请TX类型的锁,并将实际锁定的数据行的锁标志位进行置位。

在数据行上只有X锁(排他锁)。在 Oracle数据库中,当一个事务首次发起一个DML语句时就获得一个TX锁,该锁保持到事务被提交或回滚。当两个或多个会话在表的同一条记录上执行 DML语句时,第一个会话在该条记录上加锁,其他的会话处于等待状态。当第一个会话提交后,TX锁被释放,其他会话才可以加锁。由于第一个SQL语句的执行计划走全表扫描,所以导致这个事务的时间很长,会话2就一直被阻塞,直到第一个会话提交或回滚。

另外,我们都知道在Oracle中实现了细粒度的行锁row lock,且在ORACLE的内部实现中没有使用基于内存的行锁管理器,row lock是依赖于数据块本身实现的。换句话说判定一行数据究竟有没有没锁住,要求Server Process去pin住相应的block buffer并检查才能够发现。所以,对于会话1(SID=925),我们无法定位到那些行获取了TX锁。这个可以参考https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:9533876300346704362

那么问题来了,对于会话1的SQL走全表扫描,找到FINAL_GARMENT_FACTORY_CD ='KLB'的记录就会在对应的数据行的锁标志进行置位。假如FINAL_GARMENT_FACTORY_CD ='KLB'的记录位于扫描位置的末端呢? 这个实验会是什么样的结果呢?我们用下面SQL找出一些记录。

SELECT ROWID, T.* FROM INV_TEST T WHERE STOCK_DATE > SYSDATE -120

然后我们将其中一条记录使用下面脚本更新。

SQL> UPDATE INV_TEST SET FINAL_GARMENT_FACTORY_CD='KLB' WHERE ROWID='AAC1coAB4AAEuXrAAM';

 

1 row updated.

 

SQL> COMMIT;

 

Commit complete.

然后我们接下来继续上面实验, 不过第二个SQL是删除ROWID='AAC1coAB4AAEuXrAAM'这条记录,我们看看实验结果

SQL> SELECT USERENV('SID') FROM DUAL;

 

USERENV('SID')

--------------

           925

 

SQL> DELETE FROM INVSUBMAT.INV_TEST WHERE FINAL_GARMENT_FACTORY_CD ='KLB';

等了大概10秒左右,我们在会话2执行第二个SQL,发现这个时候,这个SQL2马上执行完成了。跟之前的实验现象完全不同

Oracle关于TX锁的一个有趣的问题

其实出现这样的现象,是因为第二个会话(SID=917)首先获取了这一行的TX锁, 而第一个会话由于走全表扫描,它还没扫描到这条记录。可以说在一个事务中,对记录持有X锁是有顺序和时间差的。也就是说会话(SID=917)首先在一行上获取了TX锁。

另外需要注意的是:其实关于Oracle的row lock或TX锁,虽然很多时候我们把 TX lock叫做row lock , 但是实际上它们是两回事。row lock是基于数据块实现的,而TX lock则是通过内存中的ENQUEUE LOCK实现的.它是一种保护共享资源的锁定机制,一个排队机制,先进先出(FIFO). 关于这个,这里不展开叙说。

Oracle关于TX锁的一个有趣的问题的更多相关文章

  1. 理解Oracle TM和TX锁

    在Oracle中有很多锁,通过v$lock_type视图可以查看Oracle中所有类型的锁,在本篇文章中我们熟悉一下TM和TX锁的类型 SQL> select * from v$lock_typ ...

  2. Oracle TM锁和TX锁

    CREATE TABLE "TEST6" ( "ID" ), "NAME" ), "AGE" ,), "SEX ...

  3. Oracle数据库的锁类型

    Oracle数据库的锁类型 博客分类: oracle   Oracle数据库的锁类型 根据保护的对象不同,Oracle数据库锁可以分为以下几大类:DML锁(data   locks,数据锁),用于保护 ...

  4. Oracle中的锁

    Oracle中的锁 锁是一种机制,多个事务同时访问一个数据库对象时,该机制可以实现对并发的控制 按照用户系统锁可以分为自动锁和显示锁. 自动锁(系统上锁):DML锁.DDL锁.systemlocks锁 ...

  5. oracle 死锁和锁等待的区别

    所谓的锁等待:就是一个事务a对一个数据表进行ddl或是dml操作时,系统就会对该表加上表级的排它锁,此时其他的事务对该表进行操作的时候会等待a提交或是回滚后,才可以继续b的操作 所谓的死锁:当两个或多 ...

  6. Oracle 一次 锁表 处理小记

    同事说测试库上的一张表被锁了. 不能执行DML 操作. 锁表的准确说法应该是阻塞.之前的一遍blog里有说明: 锁 死锁 阻塞Latch 等待 详解 http://blog.csdn.net/tian ...

  7. ORACLE 中的 锁 介绍

    ORACLE 中的 锁 介绍 Oracle数据库支持多个用户同时与数据库进行交互,每个用户都可以同时运行自己的事务,从而也需要对并发访问进行控制.Oracle也是用“锁”的机制来防止各个事务之间的相互 ...

  8. Oracle数据库悲观锁与乐观锁详解

    数据的锁定分为两种方法,第一种叫做悲观锁,第二种叫做乐观锁.什么叫悲观锁呢,悲观锁顾名思义,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住.而乐 ...

  9. oracle 11g杀掉锁的sql

    oracle 11g杀掉锁的sql [引用 2013-3-6 17:19:12]     字号:大 中 小 --查询出出现锁的session_idselect session_id from v$lo ...

随机推荐

  1. 【题解】【字符串】【Leetcode】Valid Palindrome

    Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignori ...

  2. HPCC 登录总结

    最近开始做NGS的分析,数据明显更大,在自己的机子上面做有些不现实了,需要登录高性能计算机. 1. 目录结构: home directory: /auto/rcf-40/USERNAME -- onl ...

  3. &lbrack;Ruby on Rails系列&rsqb;6、一个简单的暗语生成器与解释器(上)

    [0]Ruby on Rails 系列回顾 [Ruby on Rails系列]1.开发环境准备:Vmware和Linux的安装 [Ruby on Rails系列]2.开发环境准备:Ruby on Ra ...

  4. 学习笔记&lowbar;Java&lowbar;day13&lowbar;三层的HelloWorld程序(15)--不错,整体三层架构学习

    分三层写:养成一个架构的习惯,如何编写一个大型网站 DAO数据层 service业务层 servlet web表述层

  5. DatePicker的使用

    activity_main.xml: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android ...

  6. 【learning】莫比乌斯反演

    吐槽 额其实这个东西的话..好像缠着机房里面的dalao们给我讲过好多遍了然后.. 拖到现在才搞懂也是服了qwq(可能有个猪脑子) 感觉就是主要几条式子然后疯狂换元换着换着就化简运算了? 草稿纸杀手q ...

  7. Android分包原理

    如果App引用的库太多,方法数超过65536后无法编译.这是因为单个dex里面不能有超过65536个方法.为什么有最大的限制呢,因为Android会把每一个类的方法id检索起来,存在一个链表结构里面. ...

  8. 3-4 8精彩算法集合。struct(C&comma;ruby) Ruyb类对象和结构体&comma; 3-5

    在本章我遇到了c语言的struct数据,即自定义的数据结构.比如: struct edge { int u; int v; int w; }; 题目给了一组数据,用edge储存.需要按照w大小排序.我 ...

  9. PHP分页函数 学习笔记

    function smarty_function_assign_debug_info($params, &$smarty){ $assigned_vars = $smarty->_tpl ...

  10. 【BZOJ4560】&lbrack;NOI2016&rsqb;优秀的拆分

    [BZOJ4560][NOI2016]优秀的拆分 题面 bzoj 洛谷 题解 考虑一个形如\(AABB\)的串是由两个形如\(AA\)的串拼起来的 那么我们设 \(f[i]\):以位置\(i\)为结尾 ...