无法终止的 Oracle 会话正在等待 "SQL*Net message from client" 事件

Unkillable Oracle session waiting on "SQL*Net message from client" event

在 Oracle 11gR2 上,我最近遇到了一个非常有趣的情况,涉及阻塞(但空闲!)MERGE 语句挂在 "SQL*Net message from client" 事件,导致后续并发执行的 MERGE 语句通过 "cursor: pin S wait on X" 事件阻塞在第一条语句上。在 Oracle Enterprise Manager 中,可以观察到以下内容:

这种情况变得更加严重,因为上面的 Session-ID 1204 不能被杀死:

alter system kill session 'sid,serial#';
alter system kill session 'sid,serial#' immediate;

我们的 DBA 有时可以杀死操作系统进程,但通常需要重新启动整个数据库。幸运的是,到目前为止,只在测试系统上,从未在生产中。

注:

我知道这可能与这个相当模糊的问题中报告的问题类似:Oracle updates/inserts stuck, DB CPU at 100%, concurrency high, SQL*Net wait message from client。我还是会重新报告,因为我有明确的复制路径,我会报告为答案。

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

CREATE TABLE t (
  v INT, 
  s VARCHAR2(400 CHAR)
);

使用内联值进行复制

现在,运行 任何 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);

这个例子很傻,CLOB在这里被"accident"绑定了。尽管如此,这样的语句不应该在 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%';

...我得到:

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     

请注意报告的事件 ("db file sequential read") 与原始事件 ("SQL*Net message from client") 有何不同,它正在使用绑定变量

使用绑定值进行复制

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);

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

在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;
/

我得到:

          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

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