【问题标题】:Oracle: How to efficiently select rows using a key listOracle:如何使用键列表有效地选择行
【发布时间】:2013-08-07 19:31:10
【问题描述】:

我有一个 Oracle 表,其中包含一个主键(我们称之为 key)和一个 value 字段。在我的 PHP 应用程序中,我有一个键列表,我想从数据库中提取所有相应的值。我可以使用类似下面的 PHP 代码来实现:

$keyList = array('apple', 'orange', 'banana');

$conn = oci_pconnect(USERNAME, PASSWORD, URI);
$stmt = oci_parse($conn, 'SELECT * FROM myTable WHERE value IN ('
                         .explode(',', $keylist)
                         .')');
oci_execute($stmt);
while($row = oci_fetch_array($stmt, OCI_ASSOC)) {
    echo "{$row['KEY']}, {$row['VALUE']}\n"; // Print the values
}

这应该可以,但$keyList 最多可以包含 200 个项目(甚至更多)。这引发了以下问题:

  • 这是最有效/最可靠的方法吗?
  • 还是使用某种从数据库中选择单个值并为列表中的每个键执行一次的准备好的语句来执行此操作会更好?

【问题讨论】:

  • 您的方法可能是最有效的方法,除非 keyList 首先来自 Oracle(例如,如果它是另一个查询的结果,那么您想结合那些查询)。我肯定不会为列表中的每个键执行一次,因为这样的往返可能非常昂贵。至于 200 项长的列表,您必须进行试验,但我认为这不是问题。 还有一点非常重要:一定要清理keyList 中的数据,否则您可能会将您的应用暴露给 SQL 注入。
  • @EdGibbs 。 . .你应该把你的评论变成一个真正的答案。
  • 感谢@GordonLinoff 的建议;我已将此添加为答案。
  • 你在“keyvalue...

标签: php sql oracle performance


【解决方案1】:

IN 条件的值作为字符串连接传递不是一个好习惯。首先,当然是安全性和正确性,但下一点是性能。
每次调用语句时,数据库引擎都会对其进行解析,构建查询计划,然后执行 SQL 语句中指定的操作。
如果您每次都从头开始构建查询文本,那么每次都会执行所有三个阶段。
但是,如果您始终使用绑定变量,则查询看起来相同,因此数据库使用缓存的查询计划可以加快查询执行速度。甚至您也可以只调用一次 oci_parse() 并使用不同的提供参数集重用 $stmt 变量。
因此,为了获得最佳性能,您必须使用绑定变量并使用 oci_bind_array_by_name 填充数组。

另外一点是,使用oci_fetch_all 检索结果可能比逐行读取结果集执行得更快,但这取决于处理结果的逻辑。

更新

似乎只有当你要执行 PL/SQL 块并且不能将它与 SQL 语句一起使用时,传递数组参数才有效。但另一种可能性是使用collections 传递参数值列表。即使使用数组也可以满足问题的条件,但这种方式不太优雅。
除了查询数据库的不同方法之外,还有诸如系统设置之类的东西。对于 PHP,php.ini 文件中有一些参数控制与 Oracle 的交互。其中之一 (oci8.statement_cache_size) 与查询缓存和性能有关。

示例

所有示例都在 Oracle 中使用相同的数据设置。
要传递数据,我选择预定义的SYS.ODCIVarchar2List 类型,但也可以定义具有相同特征的自定义类型(在数据设置示例中演示)。 下面是演示数据方案设置和在 DML 中使用集合的原理的代码。

SQLFiddle

create table myTable(value varchar2(100), key varchar2(100))
/

insert into myTable(value, key)
select * from (
  select 'apple', 'apple_one' from dual union all
  select 'apple', 'apple_two' from dual union all
  select 'banana', 'banana_one' from dual union all
  select 'orange', 'orange_one' from dual union all
  select 'orange', 'orange_two' from dual union all
  select 'potato', 'potato_one' from dual
)
/

create or replace type TCustomList as table of varchar2(4000)
/

create or replace package TestPackage as

  type TKeyList is table of varchar2(1000) index by binary_integer;

  function test_select(pKeyList in out TKeyList) return sys_refcursor;

end;
/

create or replace package body TestPackage is

  function test_select(pKeyList in out TKeyList) return sys_refcursor
  is               
    vParam sys.ODCIVarchar2List := sys.ODCIVarchar2List();
    vCur sys_refcursor;  
    vIdx binary_integer;
  begin                

    vIdx := pKeyList.first;
    while(vIdx is not null) loop
      vParam.Extend;
      vParam(vParam.last) := pKeyList(vIdx);
      vIdx := pKeyList.next(vIdx);
    end loop;

    open vCur for 
      select * from myTable where value in (select column_value from table(vParam))    
    ;

    return vCur;
  end;

end;
/

展示集合的查询:

--select by value list
select * from myTable 
where value in (
        select column_value 
        from table(Sys.ODCIVarchar2List('banana','potato'))
      )
/

--same with custom type
select * from myTable 
where value in (
        select column_value 
        from table(TCustomList('banana','potato'))
      )
/

--same with demonstration of casting 
select * from myTable 
where value in (
        select column_value 
        from table(cast(TCustomList('banana','potato') as Sys.ODCIVarchar2List))
      )
/

示例 1 - 使用集合从 PHP 调用

<?php
  $keyList = array('apple', 'potato');

  $conn = oci_pconnect("user_name", "user_password", "SERVER_TNS_NAME");

  $stmt = oci_parse($conn, "SELECT * FROM myTable where value in (select column_value from table(:key_list))");

  $coll = oci_new_collection($conn, 'ODCIVARCHAR2LIST','SYS');

  for ($i=0; $i < count($keyList); $i++) {
    $coll->append($keyList[$i]);
  }

  oci_bind_by_name($stmt, 'key_list', $coll, -1, OCI_B_NTY);

  oci_execute($stmt);

  while($row = oci_fetch_array($stmt, OCI_ASSOC)) {
      echo "{$row['KEY']}, {$row['VALUE']}\n"; // Print the values
  }
  echo "---\n";

  $coll->free();

  //-- Run statement another time with different parameters
  //-- without reparsing.

  $coll = oci_new_collection($conn, 'ODCIVARCHAR2LIST','SYS');
  $coll->append('banana');
  oci_bind_by_name($stmt, 'key_list', $coll, -1, OCI_B_NTY);

  oci_execute($stmt);

  while($row = oci_fetch_array($stmt, OCI_ASSOC)) {
      echo "{$row['KEY']}, {$row['VALUE']}\n"; // Print the values
  }
  echo "---\n";

  $coll->free();

  oci_free_statement($stmt);
  oci_close($conn);
?>

示例 2 - 使用数组和包从 PHP 调用

<?php
  $keyList = array('apple', 'potato');

  $conn = oci_pconnect("user_name", "user_password", "SERVER_TNS_NAME");

  $stmt = oci_parse($conn, "begin :cur := TestPackage.test_select(:key_list); end;");

  $curs = oci_new_cursor($conn);

  oci_bind_array_by_name($stmt, "key_list", $keyList, 2, 100, SQLT_CHR);
  oci_bind_by_name($stmt, "cur", $curs, -1, OCI_B_CURSOR);

  oci_execute($stmt);
  oci_execute($curs);

  while($row = oci_fetch_array($curs, OCI_ASSOC)) {
      echo "{$row['KEY']}, {$row['VALUE']}\n"; // Print the values
  }
  echo "---\n";


  //-- Run statement another time with different parameters
  //-- without reparsing.

  $keyList = array('banana');

  oci_bind_array_by_name($stmt, "key_list", $keyList, 2, 100, SQLT_CHR);

  oci_execute($stmt);
  oci_execute($curs);

  while($row = oci_fetch_array($curs, OCI_ASSOC)) {
      echo "{$row['KEY']}, {$row['VALUE']}\n"; // Print the values
  }
  echo "---\n";

  oci_free_statement($stmt);
  oci_close($conn);
?>

示例 3 - 使用数组和匿名块从 PHP 调用

<?php
  $keyList = array('apple', 'potato');

  $conn = oci_pconnect("user_name", "user_password", "SERVER_TNS_NAME");

  $stmt = oci_parse($conn, "
    declare
      type TKeyList is table of varchar2(4000) index by binary_integer;

      pKeyList TKeyList := :key_list;
      vParam   sys.ODCIVarchar2List := sys.ODCIVarchar2List();
      vIdx     binary_integer;
    begin

      -- Copy PL/SQL array to a type which allowed in SQL context
      vIdx := pKeyList.first;
      while(vIdx is not null) loop
        vParam.Extend;
        vParam(vParam.last) := pKeyList(vIdx);
        vIdx := pKeyList.next(vIdx);
      end loop;

      open :cur for select * from myTable where value in (select column_value from table(vParam));
    end;
  ");

  $curs = oci_new_cursor($conn);

  oci_bind_array_by_name($stmt, "key_list", $keyList, 2, 100, SQLT_CHR);
  oci_bind_by_name($stmt, "cur", $curs, -1, OCI_B_CURSOR);

  oci_execute($stmt);
  oci_execute($curs);

  while($row = oci_fetch_array($curs, OCI_ASSOC)) {
      echo "{$row['KEY']}, {$row['VALUE']}\n"; // Print the values
  }
  echo "---\n";


  //-- Run statement another time with different parameters
  //-- without reparsing.

  $keyList = array('banana');

  oci_bind_array_by_name($stmt, "key_list", $keyList, 2, 100, SQLT_CHR);

  oci_execute($stmt);
  oci_execute($curs);

  while($row = oci_fetch_array($curs, OCI_ASSOC)) {
      echo "{$row['KEY']}, {$row['VALUE']}\n"; // Print the values
  }
  echo "---\n";

  oci_free_statement($stmt);
  oci_close($conn);
?>

【讨论】:

  • 你提出了一些有趣的观点。我搜索了如何将oci_bind_array_by_name 与SQL IN 条件一起使用。您能举例说明在这种情况下如何使用它吗?
  • @Lorax 这些论据和例子说服了你,或者你需要一些更有说服力的论据?
【解决方案2】:

您的方法可能是最有效的方法,除非 keyList 首先来自 Oracle(例如,如果它是另一个查询的结果,那么您希望合并这些查询)。

我绝对不会为列表中的每个键执行一次,因为这样的往返可能非常昂贵。至于 200 项长的列表,您必须进行试验,但我认为这不是问题。

还有一点非常重要:一定要清理keyList 中的数据,否则您可能会将您的应用暴露给 SQL 注入。

【讨论】:

    猜你喜欢
    • 2013-10-25
    • 1970-01-01
    • 2021-07-13
    • 1970-01-01
    • 2021-10-24
    • 1970-01-01
    • 2013-03-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多