我正在使用我编写的 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')
似乎使用了相同的查询计划。