您的位置:首页 > 数据库

为什么数据库有时候不能定位阻塞(Blocker)源头的SQL语句

2016-08-30 11:48 323 查看
在SQLServer数据库或OACLE数据库当中,通常一个会话持有某个资源的锁,而另一个会话在请求这个资源,就会出现阻塞(blocking)。这是DBA经常会遇到的情况。当出现SQL语句的阻塞时,很多人想查看阻塞的源头(哪个SQL语句阻塞了哪个SQL),这样方便直观、简洁明了的定位问题。但是很多时候,很多场景,我们通过SQL语句并不能或者说不容易定位到阻塞者(Blocker)的SQL语句,当然我们可以很容易找到被阻塞的SQL语句,以及它在等待的锁资源。下面我们先分析一下SQLServer数据库的这类场景,然后分析一下ORACLE数据库的这类场景。如有不足的地方,敬请指出。在SQLServer当中,我们先准备下面测试环境(测试用的表和数据)。

USETest;
GO
CREATETABLETest
(
IDINT,
NAMEVARCHAR(12)
);
INSERTINTOTest
VALUES(1000,'Kerry');
INSERTINTOTest
VALUES(1001,'Jimmy');

场景1:我们构造这样一个简单的场景,例如如下:
在会话81中执行下面SQL语句

BEGINTRAN
UPDATETestSETNAME='Tina'WHEREID=1000;


在会话72中执行下面SQL语句

SELECT*FROMTEST;


在另外一个会话窗口执行下面语句,查看阻塞(blocker)者和被阻塞者的SQL语句(这里能够定位到阻塞者(blocker)的SQL语句)。如下所示

SELECTwt.blocking_session_idASBlockingSessesionId
,sp.program_nameASBlocking_ProgramName
,COALESCE(sp.LOGINAME,sp.nt_username)ASBlocking_HostName
,ec1.client_net_addressASClientIpAddress
,db.nameASDatabaseName
,wt.wait_typeASWaitType
,ec1.connect_timeASBlockingStartTime
,wt.WAIT_DURATION_MS/1000ASWaitDuration
,ec1.session_idASBlockedSessionId
,h1.TEXTASBlockedSQLText
,h2.TEXTASBlockingSQLText
FROMsys.dm_tran_locksAStlWITH(NOLOCK)
INNERJOINsys.databasesASdbWITH(NOLOCK)
ONdb.database_id=tl.resource_database_id
INNERJOINsys.dm_os_waiting_tasksASwtWITH(NOLOCK)
ONtl.lock_owner_address=wt.resource_address
INNERJOINsys.dm_exec_connectionsec1WITH(NOLOCK)
ONec1.session_id=tl.request_session_id
INNERJOINsys.dm_exec_connectionsec2WITH(NOLOCK)
ONec2.session_id=wt.blocking_session_id
LEFTOUTERJOINmaster.dbo.sysprocessesASspWITH(NOLOCK)
ONSP.spid=wt.blocking_session_id
CROSSAPPLYsys.dm_exec_sql_text(ec1.most_recent_sql_handle)ASh1
CROSSAPPLYsys.dm_exec_sql_text(ec2.most_recent_sql_handle)ASh2




但是这个场景是一个非常理想化的场景,实际场景中,可能会话81接下来会去执行其它SQL语句,它并不会一直停留在这个SQL语句上,例如,我们在会话81中执行SELECTGETDATE();这个SQL语句

BEGINTRAN
UPDATETestSETNAME='Tina'WHEREID=1000;
SELECTGETDATE();




如上所示,此时查到的Blocker者的SQL语句为"SELECTGETDATE();",而这个SQL其实和被阻塞的SQL没有半毛关系。即使使用sp_WhoIsActive这样专业的SQL亦是如此。



当然我们可以查看其等待的锁对象信息,这也是我们所能追踪、捕获的。如下所示:

<Databasename="Test">
<Locks>
<Lockrequest_mode="S"request_status="GRANT"request_count="1"/>
</Locks>
<Objects>
<Objectname="Test"schema_name="dbo">
<Locks>
<Lockresource_type="OBJECT"request_mode="IS"request_status="GRANT"request_count="1"/>
<Lockresource_type="PAGE"page_type="*"request_mode="IS"request_status="GRANT"request_count="1"/>
<Lockresource_type="RID"page_type="*"request_mode="S"request_status="WAIT"request_count="1"/>
</Locks>
</Object>
</Objects>
</Database>




这种场景,如果只是某个会话发出的即席查询,那么你几乎已经很难捕获到阻塞的源头UPDATETestSETNAME='Tina'WHEREID=1000这个SQL语句了。除非你结合其它一些手段,逆向推断。
场景2:上面查找SQL阻塞的SQL语句,有时候只能定位到某一个存储过程或一大段即席查询SQL。
例如,下面一个构造的存储过程,一个用户正在一个会话当中执行它,

CREATEPROCEDUREPRC_TEST
AS
BEGIN
BEGINTRANTR1
UPDATETestSETNAME='YourName'WHEREID=1000;
SELECT*FROMsys.sysprocessesWHEREspid=@@SPID;
WAITFORDELAY'00:00:20';
COMMITTRANTR1;
END
GO

另外一个用户在另外一个会话执行下面查询SQL语句

SELECT*FROMTEST;

查看阻塞的历史记录



你会看到捕获的是整个存储过程,当然这个测试案例很容易知道是那个SQL语句阻塞了,实际的存储过程可能业务很复杂,SQL语句也非常多,你想从一个存储过程里面找到阻塞者(Blocker)的SQL语句其实是非常麻烦的。需要你仔细甄别,当存储过程的业务逻辑复杂,SQL语句非常多时,这是一个头痛的事情
其实遇到这些场景,我们大可不必一定要查看阻塞这(Blocker)的具体SQL,我们只需要查看被阻塞者,等待的锁对象资源的相关信息即可,你可以大致判断到底是一个什么类型的SQL导致了这类阻塞。
那么我们接下来看看ORACLE数据库场景吧。我们先准备一个测试环境(测试表和相关数据)

CREATETABLE"TEST"."TEST"
("ID"NUMBER,
"NAME"VARCHAR2(12)
);
INSERTINTOTEST
SELECT1001,'jimmy'FROMDUALUNIONALL
SELECT1002,'Kerry'FROMDUAL;
COMMIT;

接下来我们在会话窗口一执行下面SQL:

[oracle@DB-Server~]$sqlplustest/test
SQL*Plus:Release11.2.0.1.0ProductiononTueAug3010:16:432016
Copyright(c)1982,2009,Oracle.Allrightsreserved.
Connectedto:
OracleDatabase11gEnterpriseEditionRelease11.2.0.1.0-64bitProduction
WiththePartitioning,OLAP,DataMiningandRealApplicationTestingoptions
SQL>showuser;
USERis"TEST"
SQL>UPDATETESTSETNAME='KKK'WHEREID=1001;
1rowupdated.
SQL>

在另外一个会话窗口二执行下面SQL

[oracle@DB-Server~]$sqlplustest/test
SQL*Plus:Release11.2.0.1.0ProductiononTueAug3010:17:222016
Copyright(c)1982,2009,Oracle.Allrightsreserved.
Connectedto:
OracleDatabase11gEnterpriseEditionRelease11.2.0.1.0-64bitProduction
WiththePartitioning,OLAP,DataMiningandRealApplicationTestingoptions
SQL>showuser;
USERis"TEST"
SQL>UPDATETESTSETNAME='Ken'WHEREID=1001;

然后我们在第三个窗口执行下面SQL语句,查看阻塞和被阻塞的SQL语句

SELECTdba_objects.object_name,
locks_t.row#,
locks_t.blocked_secs,
locks_t.blocker_text,
locks_t.blocked_text,
locks_t.blocked_sql_text,
locks_t.blocking_sql_text
FROM(SELECT/*+NO_MERGE*/
blocking_lock_session.username||'@'||blocking_lock_session.machine||'(SID='||blocking_lock_session.sid||')['||
blocking_lock_session.program||'/PID='||blocking_lock_session.process||']'asblocker_text,
blocked_lock_session.username||'@'||blocked_lock_session.machine||'(SID='||blocked_lock_session.sid||')['||
blocked_lock_session.program||'/PID='||blocked_lock_session.process||']'asblocked_text,
blocked_lock_session.row_wait_obj#,
blocked_lock_session.row_wait_file#,
blocked_lock_session.row_wait_block#,
blocked_lock_session.row_wait_row#,
DBMS_ROWID.ROWID_CREATE(1,
blocked_lock_session.row_wait_obj#,
blocked_lock_session.row_wait_file#,
blocked_lock_session.row_wait_block#,
blocked_lock_session.row_wait_row#)row#,
blocked_lock_session.seconds_in_waitblocked_secs,
blocked_sql.sql_textblocked_sql_text,
blocking_sql.sql_textblocking_sql_text
FROMv$lockblocking_lock,
v$sessionblocking_lock_session,
v$lockblocked_lock,
v$sessionblocked_lock_session,
v$sqlblocked_sql,
v$sqlblocking_sql
WHEREblocking_lock.block=1
ANDblocking_lock.id1=blocked_lock.id1
ANDblocking_lock.id2=blocked_lock.id2
ANDblocked_lock.request>0
ANDblocking_lock.sid=blocking_lock_session.sid
ANDblocked_lock.sid=blocked_lock_session.sid
ANDblocked_lock_session.sql_id=blocked_sql.sql_id
ANDblocked_lock_session.sql_child_number=blocked_sql.child_number
ANDblocking_lock_session.PREV_SQL_ADDR(+)=blocking_sql.ADDRESS
)locks_t,
dba_objects
WHERElocks_t.row_wait_obj#=dba_objects.object_id
ORDERBYlocks_t.blocked_secs;




如果我们在会话窗口1,再执行一个语句,如下所示

SQL>showuser;
USERis"TEST"
SQL>UPDATETESTSETNAME='KKK'WHEREID=1001;
1rowupdated.
SQL>select*fromdual;
D
-
X

此时捕获到的是select*fromdual;这个SQL跟被阻塞的SQL没有任何关系,当然如果你继续在会话窗口执行其它SQL语句,捕获的都是不相关的SQL语句,已经没有任何意义



出现这个问题,是因为当一个会话正在执行某个SQL语句,那么v$session视图中的SQL_ID记录的是正在执行SQL的SQL_ID,当会话空闲或执行其它SQL语句后,SQL_ID就会变化,PRE_SQL_ID记录上一个执行完的SQL的SQL_ID值,PREV_SQL_ADDR也是如此。如下英文所述
AccordingtotheReferenceManualentryforV$SESSIONtheSQL_IDcolumnrepresentsthecurrentSQLstatementbeingexecutedbyasession.IfthesessionisidlethereisnocurrentSQLstatement.AlsoifasessionperformsanupdatethenperformsaquerytheSQL_IDwouldreflectthequeryandnottheupdatewhichisthestatementthatisblocking.ThereisinfactnoquerythatisguaranteedtofindtheblockingSQL.UnlesstheblockingstatementisthecurrentstatementallyoucanfindforsureItheblockingsession
如果你不用SQL*Plus,使用PL/SQLDeveloper这个工具,你会看到BLOCKING_SQL_TEST永远都是beginsys.dbms_output.get_line(line=>:line,status=>:status);end;这个是因为PL/SQLDeveloper在执行完SQL后,会调用其它SQL语句,当然SQLDeveloper不会有这样的问题。
所以综上述,想要找到阻塞的源头SQL语句,只用SQL查询,其实在很多场景是不太现实的,所以很多SQL语句都只给出阻塞者的会话信息或锁定对象信息。如下所示
会话ID为8的会话执行下面SQL

UPDATETESTSETNAME='TEST'WHEREID=1001;

会话ID为137的会话执行下面SQL

UPDATETESTSETNAME='TES1'WHEREID=1001;

然后我们使用get_locked_objects_rpt.sql查看被阻塞的SQL,以及锁定相关对象的信息(get_locked_objects_rpt.sql请参考get_locked_objects_rpt.sql)

SQL>@get_locked_objects_rpt.sql
Entervaluefor1:6
old42:ANDlocks_t.blocked_secs>&1
new42:ANDlocks_t.blocked_secs>6
=========$Revision:1.4$($Date:2013/09/1613:15:22$)===========
Lockedobject:TEST
Lockedrow#:AAASEkAAEAAAADVAAA
Blockedfor:19seconds
Blockerinfo.:TEST@GFG1\GET253194(SID=8)[plsqldev.exe/PID=17988:14616]
Blockedinfo.:TEST@get253194(SID=137)[SQLDeveloper/PID=17780]
BlockedSQL:UPDATETESTSETNAME='TES1'WHEREID=1001
Found1blockedsession(s).
DisconnectedfromOracleDatabase11gEnterpriseEditionRelease11.2.0.1.0-64bitProduction
WiththePartitioning,OLAP,DataMiningandRealApplicationTestingoptions
[oracle@DB-Server~]$





然后我通过上面的LockedObject知道被锁定的对象为Test表的ROWID为AAASRCAAEAAAADVAAA的记录,如下所示


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐