无法终止的Oracle会话等待"来自客户端的SQL*Net消息"事件

Luk*_*der 5 sql oracle concurrency contention sql-merge

在Oracle 11gR2上,我最近遇到了一个非常有趣的情况,涉及阻塞(但是空闲!)MERGE语句,该语句挂起"来自客户端的SQL*Net消息"事件,导致后续的,并发执行的MERGE语句在第一个语句上阻塞" cursor:pin S等待X"事件.在Oracle Enterprise Manager中,可以观察到以下内容:

在此输入图像描述

这种情况变得更加严重,因为上述Session-ID 1204无法被杀死:

alter system kill session 'sid,serial#';
alter system kill session 'sid,serial#' immediate;
Run Code Online (Sandbox Code Playgroud)

我们的DBA有时会破坏操作系统进程,但通常需要重新启动整个数据库.幸运的是,到目前为止,仅在测试系统上,从未投入生产.

注意:

我知道这可能是一个类似于这个相当含糊的问题中报告的类似问题:Oracle更新/插入卡住,DB CPU为100%,并发性高,来自客户端的SQL*Net等待消息.我还会再次报告,因为我有一个清晰的再现路径,我将作为答案报告.

Luk*_*der 9

CLOB数据类型用作传递给MERGE语句ON子句的值时,这似乎是Oracle中的一个错误.假设这个数据库:

CREATE TABLE t (
  v INT, 
  s VARCHAR2(400 CHAR)
);
Run Code Online (Sandbox Code Playgroud)

使用内联值进行复制

现在,在任何Oracle客户端中运行以下语句,包括SQL*Plus,SQL Developer或JDBC,这有助于非常轻松地重现问题(我正在使用Oracle 11g XE 11.2.0.2.0):

MERGE INTO t                      
USING (
  SELECT 
    1 v, 
    CAST('abc' AS CLOB) s 
  FROM DUAL
) s 
ON (t.s = s.s) -- Using a CLOB here causes the bug.
WHEN MATCHED THEN UPDATE SET
  t.v = s.v        
WHEN NOT MATCHED THEN INSERT (v, s) 
VALUES (s.v, s.s);
Run Code Online (Sandbox Code Playgroud)

这个例子很愚蠢,CLOB这里被"意外"所束缚.尽管如此,这样的声明不应该在Oracle中创建一个僵尸会话,但它就在那里.我在SQL*Plus中运行上述语句三次然后运行这个...

SELECT 
  s.sid,
  s.serial#,
  s.sql_id,
  s.event,
  s.blocking_session,
  q.sql_text
FROM v$session s
JOIN v$sql q
ON s.sql_id = q.sql_id
WHERE s.username = 'TEST'
AND UPPER(TRIM(q.sql_text)) LIKE 'MERGE%';
Run Code Online (Sandbox Code Playgroud)

......我明白了:

sid serial# sql_id          event                       blocking_session
9   3       82a2k4sqzy1jq   cursor: pin S wait on X     92
49  89      82a2k4sqzy1jq   cursor: pin S wait on X     92
92  13      82a2k4sqzy1jq   db file sequential read     
Run Code Online (Sandbox Code Playgroud)

请注意报告的事件与原始事件("客户端的SQL*Net消息")之间的差异("db file sequential read "),它使用的是绑定变量

使用绑定值进行复制

var v_s varchar2(50)
exec :v_s := 'abc'

MERGE INTO t                      
USING (
  SELECT 
    1 v, 
    CAST(:v_s AS CLOB) s 
  FROM DUAL
) s 
ON (t.s = s.s) -- Using a CLOB here causes the bug.
WHEN MATCHED THEN UPDATE SET
  t.v = s.v        
WHEN NOT MATCHED THEN INSERT (v, s) 
VALUES (s.v, s.s);
Run Code Online (Sandbox Code Playgroud)

在SQL*Plus中运行的上述语句也会产生错误:

sid serial# sql_id          event                           blocking_session
8   1       4w9zuxrumumgj   SQL*Net message from client     
90  7       4w9zuxrumumgj   cursor: pin S wait on X         8
94  21      4w9zuxrumumgj   cursor: pin S wait on X         8
Run Code Online (Sandbox Code Playgroud)

PL/SQL中没有复制

有趣的是,在以下PL/SQL语句中避免了该错误:

DECLARE
  v_s CLOB := 'abc';
BEGIN
  MERGE INTO t                      
  USING (
    SELECT 
      1 v, 
      CAST(v_s AS CLOB) s 
    FROM DUAL
  ) s 
  ON (t.s = s.s) -- Using a CLOB here causes the bug.
  WHEN MATCHED THEN UPDATE SET
    t.v = s.v        
  WHEN NOT MATCHED THEN INSERT (v, s) 
  VALUES (s.v, s.s);
END;
/
Run Code Online (Sandbox Code Playgroud)

我越来越:

          CAST(v_s AS CLOB) s
          *
ERROR at line 8:
ORA-06550: line 8, column 11:
PL/SQL: ORA-00932: inconsistent datatypes: expected - got CLOB
ORA-06550: line 4, column 7:
PL/SQL: SQL Statement ignored
Run Code Online (Sandbox Code Playgroud)

看起来好像PL/SQL引擎可以保护客户端免受此SQL引擎错误的影响.