简体   繁体   English

为什么在PHP中使用PDO的某些类型的准备查询速度慢?

[英]Why are certain types of prepared queries using PDO in PHP with MySQL slow?

When using SELECT * FROM table WHERE Id IN ( .. ) queries with more than 10000 keys using PDO with prepare()/execute(), the performance degrades ~10X more than doing the same query using mysqli with prepared statements or PDO without using prepared statements. 当使用SELECT * FROM table WHERE Id IN ( .. )使用带有prepare()/ execute()的PDO查询超过10000个键时,性能比使用mysqli使用预处理语句或PDO执行相同查询的性能降低了大约10倍而不使用准备好的陈述

More strange details: 更奇怪的细节:

  • More typical SELECT statements that don't have the WHERE Id IN( ..) clause perform fine even with 100K+ rows. 更典型的没有WHERE Id IN( ..)子句的SELECT语句即使使用100K +行也能正常运行。 SELECT * FROM table WHERE Id for example is fast. SELECT * FROM table WHERE Id例如很快。

  • The performance degradation occurs after prepare()/execute() is complete - it's entirely in PDOStatement::fetch() or PDOStatement::fetchAll() . 性能降低发生在prepare()/ execute()完成之后 - 它完全在PDOStatement::fetch()PDOStatement::fetchAll() The MySQL query execution time is tiny in all cases - this isn't a case of a MySQL optimization. MySQL查询执行时间在所有情况下都很小 - 这不是MySQL优化的情况。

  • Splitting the 10K query into 10 queries with 1K keys is performant. 使用1K密钥将10K查询拆分为10个查询是高性能的。

  • Using mysql, mysqli with prepared statements, or PDO without prepared statements is performant. 使用mysql,mysqli和预准备语句,或PDO没有预处理语句是高性能的。

  • PDO w/prepared takes ~6 seconds on the example below, while the others take ~0.5s. 准备好的PDO在下面的例子中需要大约6秒,而其他PDO需要大约0.5秒。

  • It gets worse in a non-linear fashion the more keys you have. 它以非线性方式变得更糟,你拥有的键越多。 Try 100K keys. 试试100K钥匙。

Sample code: 示例代码:

// $imageIds is an array with 10K keys
$keyCount = count($imageIds);
$keys = implode(', ', array_fill(0, $keyCount, '?'));
$query = "SELECT * FROM images WHERE ImageID IN ({$keys})";
$stmt = $dbh->prepare($query);
$stmt->execute($imageIds);
// until now, it's been fast.  fetch() is the slow part
while ($row = $stmt->fetch()) {
    $rows[] = $row;
}

Make sure you're telling PDO that the value is an integer not a string; 确保告诉PDO该值是整数而不是字符串; if PDO puts it as a string, then MySQL will have to typecast the values for comparison. 如果PDO将其作为字符串,那么MySQL将必须对这些值进行类型转换以进行比较。 Depending on how it goes about this, it could cause major slowdowns by causing MySQL to avoid using an index. 根据它的具体情况,它可能会导致MySQL避免使用索引,从而导致严重的减速。

I'm not completely sure about the behaviour here, but I have had this problem with Postgres a few years back... 我不完全确定这里的行为,但几年前我和Postgres有过这个问题......

There are some major mistakes on the sample code. 示例代码有一些重大错误。 So to be more precise. 所以更准确。

// $imageIds is an array with 10K keys
$keyCount = count($imageIds);
$keys = implode(', ', array_fill(0, $keyCount, '?'));
$query = "SELECT * FROM images WHERE ImageID IN ({$keys})";

so far the above code will provide something like this... 到目前为止,上面的代码将提供这样的东西......

SELECT * FROM images WHERE ImageID IN (?, ?, ?, ?, ?, ?,...?, ?, ?, ?)

There is no loop for binding... There should be a small loop in which you would bind all of the parameters being passed to MySQL. 没有绑定循环......应该有一个小循环,你可以绑定传递给MySQL的所有参数。 You go from prepare to execute . 你从prepareexecute When correct binding is primarily what you want. 正确绑定主要是您想要的。

$stmt = $dbh->prepare($query);
$stmt->execute($imageIds);
// until now, it's been fast.  fetch() is the slow part
while ($row = $stmt->fetch()) {
    $rows[] = $row;
}

Now i have a simple logic question on this part of the question... 现在我对这部分问题有一个简单的逻辑问题......

When using SELECT * FROM table WHERE Id IN ( .. ) queries with more than 10000 keys using PDO with prepare()/execute(), the performance degrades ~10X more than doing the same query using mysqli with prepared statements or PDO without using prepared statements. 当使用SELECT * FROM table WHERE Id IN ( .. )使用带有prepare()/ execute()的PDO查询超过10000个键时 ,性能比使用mysqli使用预处理语句或PDO执行相同查询的性能降低了大约10倍而不使用准备好的陈述

Would it not be better if the same query was re-written so that you would not need to pass 10000 keys as parameters? 如果重写相同的查询以便您不需要传递10000个键作为参数,那会不会更好?

PDO and MySQLi do not have major differences in timings. PDOMySQLi在时间上没有重大差异。 Bad written queries do. 糟糕的书面查询。 Very complex Stored Procedures sometimes might turn out slow if they are not well optimized. 如果没有很好地优化,非常复杂的存储过程有时可能会变慢。

Check if another query could fetch the desired result. 检查另一个查询是否可以获取所需的结果。 For example 例如

Create a small table named test 创建一个名为test的小表

create table `test` (
  `id` int(10) not null,
  `desc` varchar(255)
  ); 
insert into `test` (`id`,`desc`) values (1,'a'),(10,'a1'),(11,'a2'),(12,'a3'),(13,'a4'),(14,'a5'),(15,'a6'),(2,'ab'),(20,'ab1'),(21,'ab2'),(22,'ab3'),(23,'ab4'),(24,'ab5'),(25,'ab6');

Run those simple queries 运行这些简单的查询

select * from `test` where `id` rlike '^1$';
select * from `test` where `id` rlike '^1+';
select * from `test` where `id`=1;
select * from `test` where `id` rlike '^1.$';
select * from `test` where `id` rlike '.2$';
select * from `test` where `id` rlike '^2$';
select * from `test` where `id` rlike '.(2|3)'; // Slower
select * from `test` where `id` IN (12,13,22,23); // Faster
select * from `test` where `id` IN ('12,13,22,23'); // Wrong result
select * from `test` where `id` IN ('12','13','22','23'); // Slower

The last 4 queries have the same result in this example. 在此示例中,最后4个查询具有相同的结果。 I think that most of the times if you check it on SQLFiddle you would get query times that correspond to label that they have been given. 我认为大多数情况下,如果你在SQLFiddle上检查它,你会得到与它们已经给出的标签相对应的查询时间。

Don't have any experience with PDO so can't help with that but this method is pretty performant, although it's a bit ugly in places ;) 没有PDO的任何经验,所以无法帮助,但这种方法非常高效,虽然在某些地方有点难看;)

PHP PHP

<?php

$nums = array(); $max = 10000;

for($i=0;$i<$max*10;$i++) $nums[] = $i;

$conn = new mysqli("127.0.0.1", "vldb_dbo", "pass", "vldb_db", 3306);

$sql = sprintf("call list_products_by_id('%s',0)", implode(",",array_rand($nums, $max)));

$startTime = microtime(true);

$result = $conn->query($sql);

echo sprintf("Fetched %d rows in %s secs<br/>", 
    $conn->affected_rows, number_format(microtime(true) - $startTime, 6, ".", ""));

$result->close();
$conn->close();

?>

Results 结果

select count(*) from product;
count(*)
========
1000000

Fetched 1000 rows in 0.014767 secs
Fetched 1000 rows in 0.014629 secs

Fetched 2000 rows in 0.027938 secs
Fetched 2000 rows in 0.027929 secs

Fetched 5000 rows in 0.068841 secs
Fetched 5000 rows in 0.067844 secs

Fetched 7000 rows in 0.095199 secs
Fetched 7000 rows in 0.095184 secs

Fetched 10000 rows in 0.138205 secs
Fetched 10000 rows in 0.134356 secs

MySQL MySQL的

drop procedure if exists list_products_by_id;

delimiter #

create procedure list_products_by_id
(
in p_prod_id_csv text,
in p_show_explain tinyint unsigned
)
proc_main:begin

declare v_id varchar(10);
declare v_done tinyint unsigned default 0;
declare v_idx int unsigned default 1;

    create temporary table tmp(prod_id int unsigned not null)engine=memory; 

    -- split the string into tokens and put into a temp table...

    if p_prod_id_csv is not null then
        while not v_done do
            set v_id = trim(substring(p_prod_id_csv, v_idx, 
                if(locate(',', p_prod_id_csv, v_idx) > 0, 
                        locate(',', p_prod_id_csv, v_idx) - v_idx, length(p_prod_id_csv))));

                if length(v_id) > 0 then
                set v_idx = v_idx + length(v_id) + 1;
                        insert ignore into tmp values(v_id);
                else
                set v_done = 1;
                end if;
        end while;
    end if;

    if p_show_explain then

        select count(*) as count_of_tmp from tmp;

        explain
        select p.* from product p
        inner join tmp on tmp.prod_id = p.prod_id order by p.prod_id;

    end if;

    select p.* from product p
        inner join tmp on tmp.prod_id = p.prod_id order by p.prod_id;

    drop temporary table if exists tmp;

end proc_main #

delimiter ;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM