前阵子有一个网友在群里问了一个关于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执行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马上执行完成了。跟之前的实验现象完全不同

其实出现这样的现象,是因为第二个会话(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. ajax方法data参数用法的总结

    源文件分析: data的传递格式有两种:一是url字符串格式:一种是Json格式,格式分别如上 区别是:当传递的参数中包含 特殊字符如:&时,服务器解析这个参数时就会出错,而必须用encode ...

  2. 详解select()函数---

    以后看 http://hi.baidu.com/bimufo/item/139700e4d880cba1c00d755c

  3. The web.config file for this project is missing the required DirectRequestModule.

    The web.config file for this project is missing the required DirectRequestModule.   将应用程序集的模式由集成改为经典 ...

  4. SPFA算法——最短路径

    粗略讲讲SPFA算法的原理,SPFA算法是1994年西南交通大学段凡丁提出 是一种求单源最短路的算法 算法中需要用到的主要变量 int n;  //表示n个点,从1到n标号 int s,t;  //s ...

  5. 利用C++创建DLL并C#调用

    日期:2018年11月26日 环境:window 10,VS2015 community 一.利用C++创建DLL 1.新建项目: 2.打开CreateDLL.cpp文件,并输入测试代码 #inclu ...

  6. 数据库路由中间件MyCat - 源代码篇(1)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 进入了源代码篇,我们先从整体入手,之后拿一个简单流程前端连接建立与认证作为例子,理清代码思路和设计模式.然后 ...

  7. 51nod1202【DP-树状数组维护】

    思路: DP[i]代表从1 到 i 以 a[i] 为末尾的子序列个数,dp[i]=dp[i]+dp[j](a[i]!=a[j]) +1 利用树状数组维护以值 a[i] 结尾的子序列个数. #inclu ...

  8. mysql 快速导入大SQL文件

    进入mysql mysql -u root -p 创建数据库 CREATE DATABASE 数据库名; 设置参数 set sql_log_bin=OFF;//关闭日志 ;//关闭autocommit ...

  9. 静态文件与APP

    目录 静态文件的配置和使用 什么是静态文件? 为什么使用静态文件 如何配置,使用静态文件 静态文件相关(动态配置) app创建预注册 app指什么? 创建一个APP 注册app app文件作用 app ...

  10. values.xml:3:5-58:857: AAPT: error: resource android:attr/fontVariationSettings not found.

    修改app/build.gradle中的版本 compileSdkVersion 28 targetSdkVersion 28 具体不知道为何要修改为28,但在android/build.gradle ...