0

我正在使用我编写的 PHP 脚本(复制如下)使用 oci 函数从给定 Oracle 数据库的不同模式中检索数据。昨天,我注意到一个到目前为止 100% 可重现的问题,我很难理解。

问题是:我有一个简单的查询(“select * from table where id = '42'”)和两个模式 S1 和 S2。首先我连接到数据库,然后对于每个模式,我更改 current_schema 并执行查询。如果我在 S1 上执行此操作,它会返回 0 结果,这很好。如果我在 S2 上执行此操作,它会返回 2 个结果,这很好。如果我在 S1 然后 S2 上执行此操作,它将在 S1 上返回 0 结果(这很好),并且 1858 - ORA-01858: a non-numeric character was found where a numeric was expected在尝试在 S2 上执行查询时出现错误。相应的非数字字符似乎是“选择 *”中的“*”。

由于我怀疑 Oracle 缓存问题,我决定在执行查询之前在查询末尾附加一个随机注释。这确实解决了这个问题。这是相当骇人听闻的,并且出于性能原因可能是错误的。

由于我无法在 SQL 开发人员中重现此问题,因此问题也可能来自我的代码本身,但随机后缀解决了问题的事实让我认为 Oracle 缓存做错了事。

所以这是我的代码:第一部分包含用于包装 oci 函数的有用函数,第二部分是几个不同测试的实际运行:BUG 是错误场景(ORA-01858),RAND 是带有随机后缀的场景(很好) , SCHEMA_1 是只有第一个模式的场景(0 结果:很好),SCHEMA_2 是只有第二个模式的场景(2 个结果:很好),SAME_SCHEMA 是第二个模式两次的场景(2 个结果两次:很好)。

<?php

function db_trigger_error($e) // http://php.net/manual/en/function.oci-error.php
{
    $message =
        "\n<pre>\n" .
        htmlentities($e['code']) . " - " .
        htmlentities($e['message']) . " : \n" .
        htmlentities(str_replace(array("\r\n","\t"),array("  "," "),$e['sqltext'])) .
        sprintf("\n%" . ($e['offset']+1) . "s", "^") .
        "\n</pre>\n";
    throw new Exception($message);
}

function execute_query($conn,$query,$fetch_wanted,&$fetch,&$result)
{
    echo($query . "; ...<br>\n");

    // PARSE
    $stid = oci_parse($conn, $query);
    if (!$stid) { db_trigger_error(oci_error($conn)); }
    $type = oci_statement_type($stid); 

    // EXECUTE
    $exe = oci_execute($stid);
    if (!$exe) { db_trigger_error(oci_error($stid)); }

    // FETCH ARRAY RESULT
    $update = oci_num_rows($stid);
    $fetch = $fetch_wanted  ?
        oci_fetch_all($stid, $result,null,null,OCI_FETCHSTATEMENT_BY_COLUMN) :
        0;

    // FREE STUFF
    oci_free_statement($stid);
    echo(" ... returned $fetch row" . ($fetch>1?'s':'') . " and updated/inserted/deleted $update row" . ($update>1?'s':'') . " ($type)<br>\n");
}

function connect_db(&$conn,$username,$password,$connec_string)
{   
    echo("Connecting to username/password@connec_string<br>\n");
    $conn = oci_connect($username,$password,$connec_string);
    if (!$conn) { db_trigger_error(oci_error()); }
    echo("Connected to Server " . oci_server_version($conn) . " with Client version " . oci_client_version($conn) . "<br>\n");
}

function close_connect_db($conn)
{
    if ($conn)
    {
        echo("Connection closed<br>\n");
        oci_close($conn);
    } else 
    {
        echo("No connection to close<br>\n");
    }
}


$username="USERNAME";
$password="PASSWORD";
$connec_string="(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=HOST)(PORT=PORT)))(CONNECT_DATA=(SERVICE_NAME=SERVICE)))";
$query="select * from TABLE t where t.T_ID = '035DA31100000000'";

foreach(array('BUG','RAND','SCHEMA_1','SCHEMA_2','SAME_SCHEMA') as $testCase)
{
    echo "Start $testCase<br>\n";
    $schemas=($testCase=='SCHEMA_1') ?    array('S1') :
             ($testCase=='SCHEMA_2') ?    array('S2') :   
             ($testCase=='SAME_SCHEMA') ? array('S2','S2') :
                                          array('S1','S1');

    connect_db($conn,$username, $password, $connec_string);
    foreach($schemas as $schema)
    {
        $suffix=($testCase=='RAND') ? ' --' . rand() : '';
        $query_schema="alter session set current_schema=$schema";
        try 
        {
            execute_query ($conn,$query_schema,   false,$fetch,$result,false);
            execute_query($conn, $query . $suffix,true, $fetch,$result,false);
        }  catch (Exception $e)
        {
            echo 'Caught exception: ',  $e->getMessage(), "\n";
            $fetch=0;
        }
    }
    close_connect_db($conn);
    echo "End $testCase<br>\n";
}
?>

相应的输出是:

Start BUG
Connecting to username/password@connec_string
Connected to Server Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production With the Partitioning and Data Mining options with Client version 10.2.0.3.0
alter session set current_schema=S1; ...
... returned 0 row and updated/inserted/deleted 0 row (ALTER)
select * from TABLE t where t.T_ID = '035DA31100000000'; ...
... returned 0 row and updated/inserted/deleted 0 row (SELECT)
alter session set current_schema=S2; ...
... returned 0 row and updated/inserted/deleted 0 row (ALTER)
select * from TABLE t where t.T_ID = '035DA31100000000'; ...

Warning: oci_execute() [function.oci-execute]: ORA-01858: a non-numeric character was found where a numeric was expected in /users/myself/public_html/ProofOfConcept.php on line 25
Caught exception:
1858 - ORA-01858: a non-numeric character was found where a numeric was expected : 
select * from TABLE t where t.T_ID = '035DA31100000000'
       ^
Connection closed
End BUG

Start RAND
Connecting to username/password@connec_string
Connected to Server Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production With the Partitioning and Data Mining options with Client version 10.2.0.3.0
alter session set current_schema=S1; ...
... returned 0 row and updated/inserted/deleted 0 row (ALTER)
select * from TABLE t where t.T_ID = '035DA31100000000' --1458191218; ...
... returned 0 row and updated/inserted/deleted 0 row (SELECT)
alter session set current_schema=S2; ...
... returned 0 row and updated/inserted/deleted 0 row (ALTER)
select * from TABLE t where t.T_ID = '035DA31100000000' --849888503; ...
... returned 2 rows and updated/inserted/deleted 0 row (SELECT)
Connection closed
End RAND

Start SCHEMA_1
Connecting to username/password@connec_string
Connected to Server Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production With the Partitioning and Data Mining options with Client version 10.2.0.3.0
alter session set current_schema=S2; ...
... returned 0 row and updated/inserted/deleted 0 row (ALTER)
select * from TABLE t where t.T_ID = '035DA31100000000'; ...
... returned 2 rows and updated/inserted/deleted 0 row (SELECT)
alter session set current_schema=S2; ...
... returned 0 row and updated/inserted/deleted 0 row (ALTER)
select * from TABLE t where t.T_ID = '035DA31100000000'; ...
... returned 2 rows and updated/inserted/deleted 0 row (SELECT)
Connection closed
End SCHEMA_1

Start SCHEMA_2
Connecting to username/password@connec_string
Connected to Server Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production With the Partitioning and Data Mining options with Client version 10.2.0.3.0
alter session set current_schema=S2; ...
... returned 0 row and updated/inserted/deleted 0 row (ALTER)
select * from TABLE t where t.T_ID = '035DA31100000000'; ...
... returned 2 rows and updated/inserted/deleted 0 row (SELECT)
alter session set current_schema=S2; ...
... returned 0 row and updated/inserted/deleted 0 row (ALTER)
select * from TABLE t where t.T_ID = '035DA31100000000'; ...
... returned 2 rows and updated/inserted/deleted 0 row (SELECT)
Connection closed
End SCHEMA_2

Start SAME_SCHEMA
Connecting to username/password@connec_string
Connected to Server Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production With the Partitioning and Data Mining options with Client version 10.2.0.3.0
alter session set current_schema=S2; ...
... returned 0 row and updated/inserted/deleted 0 row (ALTER)
select * from TABLE t where t.T_ID = '035DA31100000000'; ...
... returned 2 rows and updated/inserted/deleted 0 row (SELECT)
alter session set current_schema=S2; ...
... returned 0 row and updated/inserted/deleted 0 row (ALTER)
select * from TABLE t where t.T_ID = '035DA31100000000'; ...
... returned 2 rows and updated/inserted/deleted 0 row (SELECT)
Connection closed
End SAME_SCHEMA

有人知道这个问题吗?如果数据库端有适当的修复?使用随机评论作为后缀不好吗?提前致谢。

编辑以添加部分调查

这是在 Justin Cave 的良好建议下完成的。

我现在已经更新了 PHP 代码,这样我就可以根据随机标记检索 sql_id select * from v$sql where sql_text like '%random_part',然后我可以使用select * from table(dbms_xplan.display_cursor('sql_id')). 在“RAND”场景中,这个随机部分在我们运行两次选择查询时是不同的,在其他场景中,随机部分保留用于查询的不同运行。

然后我们得到:

  • 对于 RAND 场景

select * from v$sql where sql_text like '%random_part'返回 1 行。对应的查询计划是

        Plan hash value: 2841037776

  ----------------------------------------------------------------------------------------------------------
  | Id  | Operation                   | Name                       | Rows  | Bytes | Cost (%CPU)| Time     |
  ----------------------------------------------------------------------------------------------------------
  |   0 | SELECT STATEMENT            |                            |       |       |     1 (100)|          |
  |   1 |  TABLE ACCESS BY INDEX ROWID| TABLE                      |     1 |   283 |     1   (0)| 00:00:01 |
  |*  2 |   INDEX RANGE SCAN          | I_T_ID                     |     1 |       |     1   (0)| 00:00:01 |
  ----------------------------------------------------------------------------------------------------------

   Predicate Information (identified by operation id):             
   ---------------------------------------------------

       2 - access("BFP"."T_ID"='035DA31100000000')
  • 对于越野车场景:

select * from v$sql where sql_text like '%random_part'返回具有相同 SQL_ID 的 2 行。行的不同部分是:END_OF_FETCH_COUNT(对应 1 和 0)、PARSE_CALLS(对应 1 和 0)、BUFFER_GETS(对应 4 和 3)、OPTIMIZER_COST(对应 2 和 1)、PARSING_SCHEMA_ID(对应 193 和 203)、PARSING_SCHEMA_NAME(分别为 S1 和 S2)、CHILD_NUMBER(分别为 0 和 1)、CPU_TIME(分别为 7598 和 4093)、ELAPSED_TIME(相同)、CHILD_ADDRESS(奇怪的事情)。对应的查询计划是

 Plan hash value: 2841037776

  ----------------------------------------------------------------------------------------------------------
  | Id  | Operation                   | Name                       | Rows  | Bytes | Cost (%CPU)| Time     |
  ----------------------------------------------------------------------------------------------------------
  |   0 | SELECT STATEMENT            |                            |       |       |     2 (100)|          |
  |   1 |  TABLE ACCESS BY INDEX ROWID| TABLE                      |     5 |  1535 |     2   (0)| 00:00:01 |
  |*  2 |   INDEX RANGE SCAN          | I_T_ID                     |     5 |       |     1   (0)| 00:00:01 |
  ----------------------------------------------------------------------------------------------------------

   Predicate Information (identified by operation id):             
   ---------------------------------------------------

       2 - access("BFP"."T_ID"='035DA31100000000')

似乎使用了相同的查询计划。

4

0 回答 0