ITPub博客

首页 > Linux操作系统 > Linux操作系统 > update引起数据库阻塞

update引起数据库阻塞

原创 Linux操作系统 作者:wadekobe9 时间:2012-01-19 11:12:12 0 删除 编辑

 

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

--这条SQL只能看到排队的SQL,不能看到源头,不过能看到SQL文本内容

select p.spid pid,

        s.sid,

        s.SERIAL#,

        s.username,

        w.event,

        w.p1,

        w.P1TEXT,

        w.p2,

        w.P2TEXT,

        w.p3,

        w.P3TEXT,

        sq.SQL_TEXT,

        w.WAIT_TIME,

        w.SECONDS_IN_WAIT,

        w.STATE

   from v$session_wait w, v$session s, v$process p, v$sql sq

  where w.event not like 'SQL%' and w.sid = s.sid and s.paddr = p.addr and

        s.SQL_ADDRESS = sq.ADDRESS and s.SQL_HASH_VALUE = sq.HASH_VALUE

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

--这条SQL能看到锁住争用的对象

select object_name as 对象名称,s.sid,s.serial#,p.spid as 系统进程号

from v$locked_object l , dba_objects o , v$session s , v$process p

where l.object_id=o.object_id and l.session_id=s.sid and s.paddr=p.addr;

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

--这个能看到机器的名称,锁的种类和类型

select /*+ RULE */ ls.osuser os_user_name,   ls.username user_name,  

decode(ls.type, 'RW', 'Row wait enqueue lock', 'TM', 'DML enqueue lock', 'TX',

'Transaction enqueue lock', 'UL', 'User supplied lock') lock_type,  

o.object_name object,   decode(ls.lmode, 1, null, 2, 'Row Share', 3,

'Row Exclusive', 4, 'Share', 5, 'Share Row Exclusive', 6, 'Exclusive', null)

lock_mode,    o.owner,   ls.sid,   ls.serial# serial_num,   ls.id1,   ls.id2   

from sys.dba_objects o, (   select s.osuser,    s.username,    l.type,    

l.lmode,    s.sid,    s.serial#,    l.id1,    l.id2   from v$session s,    

v$lock l   where s.sid = l.sid ) ls  where o.object_id = ls.id1 and    o.owner

<> 'SYS'   order by o.owner, o.object_name

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

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

Tx 是行锁

TM 是表锁

 

昨天把会话编号拿出来杀的时候,已经没有了,这里看到的锁的情况有可能是暂时的

也就是说这个锁有可能只是暂时的,不一定是死锁在那里一直等待,而且出现以上情

况是update或者delete一张表的同一行时才会发生,不是同一行也不会发生,这里已

经验证过了。源头没有commitSQL是已经执行完了的,在会话工具里面的active里面

也是看不到的。假设我没有更新到这张表的被修改而没有提交的那一行,而是对表进行

ddl操作,那么一样会出现等待,这就是TM表锁在起作用,update一行数据时有两个

锁,获得的TM锁是共享锁,Tx锁是排他锁

 

alter system kill session '164,13521' immediate;

 

批量删除,这里只需把sql弄出来,在两边加个冒号就可以了

Select '    alter system kill session ' || s.sid  || ',' ||   s.serial# || ' immediate;    ' 

from v$locked_object l , dba_objects o , v$session s , v$process p

where l.object_id=o.object_id and l.session_id=s.sid and s.paddr=p.addr;

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

下面开始用huifu2做实验,先查看em工具,平均活动会话在o.2左右,单单模仿update同一行

不提交的情况试一下

我分别打开4个窗口运行下面这句话,都不提交,会发现oem里面的红色部分变成方块,application涨到了3.1左右

这样也说明了图形的高度和会话的多少没有关系

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

Update t_to_order_info Set employee_id=1375 Where order_id=6007

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

 

OS_USER_NAME  USER_NAME  LOCK_TYPE  OBJECT  LOCK_MODE  OWNER  SID  SERIAL_NUM  ID1  ID2

Administrator  HUIFU2  DML enqueue lock  T_S_EMPLOYEE  Row Share  HUIFU2  501  29676  79433  0

Administrator  HUIFU2  DML enqueue lock  T_S_EMPLOYEE  Row Share  HUIFU2  503  17912  79433  0

Administrator  HUIFU2  DML enqueue lock  T_S_EMPLOYEE  Row Share  HUIFU2  494  8657  79433  0

Administrator  HUIFU2  DML enqueue lock  T_S_EMPLOYEE  Row Share  HUIFU2  499  10685  79433  0

Administrator  HUIFU2  DML enqueue lock  T_TO_ORDER_INFO  Row Exclusive  HUIFU2  494  8657  79486  0

Administrator  HUIFU2  DML enqueue lock  T_TO_ORDER_INFO  Row Exclusive  HUIFU2  503  17912  79486  0

Administrator  HUIFU2  DML enqueue lock  T_TO_ORDER_INFO  Row Exclusive  HUIFU2  501  29676  79486  0

Administrator  HUIFU2  DML enqueue lock  T_TO_ORDER_INFO  Row Exclusive  HUIFU2  499  10685  79486  0

 

看到这里4个会话分别对这两个对象锁了四次,下面的lock_type非常明显的显示出了是dml enqueue Lock的排队了

lock_mode里面有row sharerow Exclusive ,这里共享和排他都有了,值得注意的一个地方是我在这里仅仅是

Update t_to_order_info 这张表,它确在t_s_employee表上面加了一个共享锁,因为我在更改的employee_id是有

外键约束的,他的外键参考列是t_s_employee表, 所以t_s_employee上面有共享锁,我现在再打开一个sql窗口,

t_s_employee执行Update t_s_employee Set dept_id=1 Where employee_id=253,是没有问题的,修改删除都Ok,

现在对上面update中的employee_id=1375这行操作

Update t_s_employee Set dept_id=3 Where employee_id=1375       --这个是可以更改的,虽然定位到了employee_id=1375这行,但是没有改到这个字段

Update t_s_employee Set employee_id=1234 Where employee_id=1375 --这样就又会出现等待的情况了,因为这个字段有外键并且正在使用

 

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

oem还是在3.1左右不动,已经成了一个平行的线,我现在再打开3个会话,再看看OEM的变换情况

发现红色方块涨到5.1左右去了,后面我commit了一个SQL窗口,那么红块就下来了一点,看来整

个红块的高度和会动会话的多少是有关系的

怎么解决呢?我将源头,也就是第一个发起updatesql窗口进行了commit,但是这样依次排队的

SQL只有窗口2能够执行,而后面的SQL依然全部阻塞在那个地方,所以即使找到源头的第一条SQL

解决了之后仍然不能解决整个阻塞的问题

 

 

    alter system kill session '507,2509' immediate;   

    alter system kill session '490,3804' immediate;   

    alter system kill session '510,31794' immediate;   

    alter system kill session '503,17912' immediate;   

    alter system kill session '501,29676' immediate;   

    alter system kill session '499,10685' immediate;   

    alter system kill session '507,2509' immediate;   

    alter system kill session '490,3804' immediate;   

    alter system kill session '510,31794' immediate;   

    alter system kill session '503,17912' immediate;   

    alter system kill session '501,29676' immediate;   

    alter system kill session '499,10685' immediate;   

 

 

 

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

将会话清楚后,我重新打开7SQL窗口,然后全部跑代收代付那条SQL,会发现蓝色

的方块直线上升,从中能够发现上去的只有user I/O,这里完全是把I/Oapplication

分开了,也就是说,在执行这种耗I/OCPU的大SQL时,在OEM里面发生变化的其实只有

蓝色I/O

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

回到当时阻塞的情况,我在sun2上面,铁勇在sun1上面,我这边刚刚做出批量杀掉锁住

对象的时候,他那边已经杀掉了一些会话,然后我这边运行的时候已经没有锁住对象的

会话,数据库基本上通了, 那么可不可以猜想整个数据库的阻塞源头是从sun1节点上面

发起的。当时查看数据库的会话的时候,看到绝大部分会话运行的都是下面这条SQL

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

Update T_S_TEL_RECORD set

 TEL_RECORD_ID=:TEL_RECORD_ID,

 EMPLOYEE_ID=:EMPLOYEE_ID,

 CUSTOMER_ID=:CUSTOMER_ID,

 CALLIN_NUMBER=:CALLIN_NUMBER,

 CALIN_TIME=:CALIN_TIME,

 RECALL_NUMBER=:RECALL_NUMBER,

 EXTENSION=:EXTENSION,

 OPERATION_TYPE=:OPERATION_TYPE,

 RESULT=:THISRESULT,

 CALLID=:CALLID,

 OPERATOR_TYPE_ID=:OPERATOR_TYPE_ID,

 OPERATION_DESC=:OPERATION_DESC

 where

 TEL_RECORD_ID = :TEL_RECORD_ID

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

单单就是一条update,后面和总监沟通后发现,说是这条SQL的程序就这么一条,也就是

说在点击了按钮之后,从程序发出去的SQL就只有这么一条,再从上面做的实验分析,也

许它仅仅是排队等待的SQL,源头是不是它现在还不好确定,只能说源头是它的可能性比较

小,现在开始分析当时故障时的AWR报告。

 

--Top 5 Timed Events(快速的说明整个数据库瓶颈)

sun1:

CPU time       

db file sequential read      User I/O

enq: TM - contention       Application

db file scattered read       User I/O

log file sync               Commit

 

 

sun2:

enq: TM - contention     Application

CPU time       

db file sequential Read     User I/O

db file scattered read      User I/O

row cache Lock            Concurrency

 

分析:

--CPU time

是系统消耗的CPU,初步怀疑SQL没有充分绑定变量,过多的硬解析,导致库缓冲产

生碎片。也就是说SQL性能不佳引起的,从这里应该把方向对准TOP Sql

 

--db file sequential read

sequential read一次进行单个块的读取,联系到执行计划可以得出sequential Read

一般发生在进行索引扫描时,并不意味着数据库产生系统问题,基至它大量出现都不是

一件坏事.真正要引起注意的是像enqueuelatch free等待事件

 

--db file scattered read

它是多块读入,等待事件的发起者是执行对表和索引全扫描操作的SQL语句,存在该事

件并不一定表示存在性能问题,但是如果该事件的等待时间比其他等待时间多得多,

则必须调查其原因。当SQL语句访问对象中的大多数行时,使用db file scattered read很有用处。

 

--enq: TM - contention

执行DML 期间,为防止对与DML 相关的对象进行修改,执行DML 的进程必须对该

表获得TM 锁。若在获取TM 锁的过程中发生争用,则等待enq:TM-contention 事件。

没有索引的外键列是TM 锁争用的主要原因

 

--log file sync  

当一个用户提交(commits)或者回滚(rollback),sessionredo信息需要写出

redo logfile.用户进程将通知LGWR执行写出操作,LGWR完成任务以后会通

知用户进程.这个等待事件就是指用户进程等待LGWR的写完成通知.

 

--ROW CACHE Lock

等待事件是一个共享池相关的等待事件。是由于对于字典缓冲的访问造成的。

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

 

观测一下TOP Sql

--SQL ordered by Elapsed Time(这是SQL执行时间的总合,而不是单次SQL的执行时间,Elapsed Time=CPU time+wait time

我们把整个注意力应该放在sun1上面,因为通过SQL ordered by Elapsed Time发现

sun2节点上并没用发现比较特殊的sql

但是在节点1上发现了事发时的这条SQL,非常的明显,排名1,2的就是这两条SQL

 Insert Into t_s_Tel_Record

  (Tel_Record_Id,

   Employee_Id,

   Customer_Id,

   Callin_Number,

   Calin_Time,

   Recall_Number,

   Extension,

   Operation_Type,

   Callid)

Values

  (:Tel_Record_Id,

   :Employee_Id,

   :Customer_Id,

   :Callin_Number,

   :Calin_Time,

   :Recall_Number,

   :Extension,

   :Operation_Type,

   :Callid)

---

Update t_s_Tel_Record

   Set Tel_Record_Id    = :Tel_Record_Id,

       Employee_Id      = :Employee_Id,

       Customer_Id      = :Customer_Id,

       Callin_Number    = :Callin_Number,

       Calin_Time       = :Calin_Time,

       Recall_Number    = :Recall_Number,

       Extension        = :Extension,

       Operation_Type   = :Operation_Type,

       Result           = :Thisresult,

       Callid           = :Callid,

       Operator_Type_Id = :Operator_Type_Id,

       Operation_Desc   = :Operation_Desc

 Where Tel_Record_Id = :Tel_Record_Id

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

 

--SQL ordered by CPU Time(记录了执行占CPU时间总和时间最长的top sql

--SQL ordered by gets(记录了执行行占总buffer gets(逻辑IO)TOP SQL)

节点1按等待事件第一位的是cpu Time ,我们查看一下SQL ordered by CPU Time的第一位

也是逻辑读排第一的

--

Select r.Special_Req_Id, r.Content

  From t_c_Special_Requirements r

  Left Join t_c_Special_Req_Template t

    On r.Temp_Id = t.Template_Id

 Inner Join v_Customer_Info c

    On c.Customer_Id = r.Customer_Id

 Where 1 = 1

   And Exists

 (Select c.Customer_Id

          From v_Customer_Info c

         Where c.Customer_Memo Like '%' || r.Content || '%'

           And c.Customer_Id = r.Customer_Id)

   And r.Customer_Id = :Customer_Id

   And t.Remain_Type = 1

   And t.Isbasedonorder = 0

   And t.Template_Id In

       (Select Tp.Template_Id

          From t_c_Special_Template_Page Tp

         Where Tp.Page_Id In (Select p.Page_Id

                                From t_c_Special_Remain_Page p

                               Where p.Page_Url = :Page_Url))

--

这里,我将变量替换成了实际的数字,跑着条SQL非常的快呀,而且次条SQL的执行计划也

很不错,这里不应该在造成很大的影响呀

 

 

--SQL ordered by Reads(记录了执行占总磁盘物理读的TOP SQL

--SQL ordered by Cluster Wait Time(集群等待时间的TOP SQL)

集群等待和物理读的第一名都是下面这条SQL,执行计划也较好,而且运行时间非常快,没有异常

--

 Select t_To_Order_Info.Create_Time,

       Expect_Out_Time,

       t_Tp_Partner.Partner_Name,

       t_To_Order_Info.Order_Flow_Id,

       t_Td_Order_Status.Order_Status_Desc,

       t_To_Order_Info.Customer_Id,

       t_To_Order_Info.Customer_Card_Id,

       t_To_Task.Pay_Type,

       t_To_Task.Pay_Amount,

       t_To_Order_Info.Order_Id,

       Guest_Num,

       Pnr,

       Active_Code,

       t_s_Employee.Employee_Name,

       (Select Count(*)

          From t_To_Company_Assure_Info

         Where t_To_Company_Assure_Info.Order_Id = t_To_Order_Info.Order_Id) Isassure,

       (Select Guest_Name

          From t_To_Order_Tickets

         Where t_To_Order_Tickets.Order_Id = t_To_Order_Info.Order_Id

           And Rownum <= 1) || Case Guest_Num

         When 1 Then

          ''

         Else

          ' ...'

       End Guest_Name,

       (Select Flight

          From t_To_Order_Tickets

         Where t_To_Order_Tickets.Order_Id = t_To_Order_Info.Order_Id

           And Rownum <= 1) Flight,

       (Select Departure || Arrival

          From t_To_Order_Tickets

         Where t_To_Order_Tickets.Order_Id = t_To_Order_Info.Order_Id

           And Rownum <= 1) Departure,

       (Select Departure_Time

          From t_To_Order_Tickets

         Where t_To_Order_Tickets.Order_Id = t_To_Order_Info.Order_Id

           And Rownum <= 1) Departure_Time,

       (Select Operation_Time

          From t_To_Order_Operation

         Where Order_Id = t_To_Order_Info.Order_Id

           And Ticket_Operation_Id = 18

           And Rownum < 2) Assigntime,

       t_f_Debit_Payment_Record.Pay_Status,

       If_Ascend,

       (Select Case Pymt_Type

                 When 1 Then

                  Null

                 When 2 Then

                  Ticketpartner.Partner_Name

                 When 3 Then

                  Paymentpartner.Partner_Name

               End

          From t_To_Consign_Pymt_Record

         Inner Join t_Tp_Partner Ticketpartner

            On Ticketpartner.Partner_Id =

               t_To_Consign_Pymt_Record.Ticket_Partner_Id

         Inner Join t_Tp_Partner Paymentpartner

            On Paymentpartner.Partner_Id =

               t_To_Consign_Pymt_Record.Partner_Id

         Where t_To_Consign_Pymt_Record.Order_Id = t_To_Order_Info.Order_Id

           And t_To_Consign_Pymt_Record.Consign_Pymt_Record_Id =

               (Select Max(Consign_Pymt_Record_Id)

                  From t_To_Consign_Pymt_Record Tcr

                 Where Tcr.Order_Id = t_To_Order_Info.Order_Id)) Paymentparnter,

       (Select Guest_Type

          From t_To_Order_Tickets

         Where t_To_Order_Tickets.Order_Id = t_To_Order_Info.Order_Id

           And Rownum <= 1) Guest_Type,

       t_f_Debit_Payment_Record.Transordid

  From t_To_Task

  Left Join t_To_Order_Info

    On t_To_Task.Order_Id = t_To_Order_Info.Order_Id

  Left Join t_s_Employee

    On t_To_Order_Info.Employee_Id = t_s_Employee.Employee_Id

  Left Join t_Td_Order_Status

    On t_Td_Order_Status.Order_Status_Id = t_To_Order_Info.Order_Status_Id

  Left Join t_f_Debit_Payment_Record

    On t_To_Task.Order_Id = t_f_Debit_Payment_Record.Order_Id

 Inner Join t_Tp_Partner

    On t_Tp_Partner.Partner_Id = t_To_Order_Info.Partner_Id

 Where (t_To_Task.Task_Type_Id = 6 Or t_To_Task.Task_Type_Id = 8)

   And t_To_Task.Pay_Type = 8

   And (t_To_Order_Info.Order_Status_Id In (2, 3, 5, 6))

   And (t_To_Order_Info.Order_Flow_Id Is Null Or t_To_Order_Info.Order_Flow_Id = 3)

   And t_To_Order_Info.Is_Debit_Card = 1

 Order By t_To_Order_Info.Order_Id

--

 

--SQL ordered by Parse Calls(记录了SQL的软解析次数的TOP SQL

update seq$ set increment$=:2, minvalue=:3, maxvalue=:4, cycle#=:5, order$=:6, cache=:7, highwater=:8, audit$=:9, flags=:10 where obj#=:1

感觉还是和系统表里面的序列有关系,问题也不在这里

 

 

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

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

依然看不出任何有用的信息,还是回到执行计划上面了,单看故障时发生等待的那

两条insertupdate的执行计划是非常非常小的,然后查阅了表T_S_TEL_RECORD

统计信息,是在115月最后一次做统计,现在收集一下统计信息

SQL> exec dbms_stats.gather_table_stats('gc','T_S_TEL_RECORD');

然后,在把关注点放在这张表的外键列上面来,发生enq: TM - contention等待的

情况是外键列上没有索引,但是这张表的两个外键都是有索引的,那么是否是索引

表现的不好?再次重建并收集一下索引的统计信息

 

SQL> select index_name,table_name,column_name

from user_ind_columns where table_name = 'T_S_TEL_RECORD';

 

INDEX_NAME                     TABLE_NAME                     COLUMN_NAME

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

PK_T_S_TEL_RECORD              T_S_TEL_RECORD                 TEL_RECORD_ID

TEL_RECORD_CUSTOMER_FK         T_S_TEL_RECORD                 CUSTOMER_ID

TEL_RECORD_EMPLOYEE_FK         T_S_TEL_RECORD                 EMPLOYEE_ID

TEL_RECORD_CALLID              T_S_TEL_RECORD                 CALLID

T_S_TEL_RECORD_CALIN_TIME      T_S_TEL_RECORD                 CALIN_TIME

 

SQL> Select table_name, From user_constraints Where table_name ='T_S_TEL_CALLOUT_RECORD'

 

SQL> alter index TEL_RECORD_CUSTOMER_FK Rebuild Online

SQL> alter index TEL_RECORD_EMPLOYEE_FK Rebuild Online

 

SQL> exec dbms_stats.gather_index_stats('gc','TEL_RECORD_CUSTOMER_FK');

SQL> exec dbms_stats.gather_index_stats('gc','TEL_RECORD_EMPLOYEE_FK');

 

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

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

整个等待事件唯一值得关注的还是--enq: TM - contention

 

实际上,当我们在对employees表的数据进行更新时,不仅会在数据行的头部记录行级锁,

而且还会在表的级别上添加一个表级锁。那么当D用户要删除表时,发现employees表上具

有一个表级锁,于是等待。通过这种在表级别上添加锁定的方式,我们就能够比较容易并

且高效地(因为不需要扫描表里的每一条记录来判断在表上是否有DML事务)对锁定进行

管理了。表级锁共具有五种模式,如下所示。

 

--行级排他锁(Row Exclusive,简称RX锁)

当我们进行DML时会自动在被更新的表上添加RX锁,或者也可以通过执行lock命令显式的

在表上添加RX锁。在该锁定模式下,允许其他的事务通过DML语句修改相同表里的其他数

据行,或通过lock命令对相同表添加RX锁定,但是不允许其他事务对相同的表添加排他锁(X锁)。

 

--行级共享锁(Row  Shared,简称RS锁)

通常是通过select … from  for  update语句添加的,同时该方法也是我们用来手工锁定某

些记录的主要方法。比如,当我们在查询某些记录的过程中,不希望其他用户对查询的记

录进行更新操作,则可以发出这样的语句。当数据使用完毕以后,直接发出rollback命令

将锁定解除。当表上添加了RS锁定以后,不允许其他事务对相同的表添加排他锁,但是允

许其他的事务通过DML语句或lock命令锁定相同表里的其他数据行。

 

--共享锁(Share,简称S锁)

通过lock  table  in  share  mode命令添加该S锁。在该锁定模式下,不允许任何用户更新表。

但是允许其他用户发出select …from for update命令对表添加RS锁。

 

--排他锁(Exclusive,简称X锁)

通过lock  table  in  exclusive  mode命令添加X锁。在该锁定模式下,其他用户不能对表进

行任何的DMLDDL操作,该表上只能进行查询。

 

--共享行级排他锁(Share Row Exclusive,简称SRX锁)

通过lock  table  in  share  row  exclusive  mode命令添加SRX锁。该锁定模式比行级排他锁

和共享锁的级别都要高,这时不能对相同的表进行DML操作,也不能添加共享锁。

 

 

首先是清楚这点

Select * from …… 触发的是RS

下面3DML语句触发的RX

Insert  into ……

Update   ……

Delete  from ……

 

 

 

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10678398/viewspace-715092/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2010-11-30

  • 博文量
    36
  • 访问量
    56664