Php MySQLi性能,多(单独)查询与子查询

Php MySQLi性能,多(单独)查询与子查询,php,sql,performance,mysqli,subquery,Php,Sql,Performance,Mysqli,Subquery,我需要计算不同(!)表中的行数,并将结果保存为某种统计数据。该脚本非常简单,并且可以按预期工作,但是我想知道是否最好使用带有(在本例中)8个子查询的单个查询,或者是否应该使用单独的8个查询,或者是否有更好、更快和更高级的解决方案 我将MySQLi与预处理语句一起使用,因此单个查询可以如下所示: $sql = 'SELECT (SELECT COUNT(cat1_id) FROM `cat1`), (SELECT COUNT(cat2_id) FRO

我需要计算不同(!)表中的行数,并将结果保存为某种统计数据。该脚本非常简单,并且可以按预期工作,但是我想知道是否最好使用带有(在本例中)8个子查询的单个查询,或者是否应该使用单独的8个查询,或者是否有更好、更快和更高级的解决方案

我将MySQLi与预处理语句一起使用,因此单个查询可以如下所示:

$sql = 'SELECT
            (SELECT COUNT(cat1_id) FROM `cat1`),
            (SELECT COUNT(cat2_id) FROM `cat2`),
            (SELECT COUNT(cat2_id) FROM `cat2` WHERE `date` >= DATE(NOW())),
            (SELECT COUNT(cat3_id) FROM `cat3`),
            (SELECT COUNT(cat4_id) FROM `cat4`),
            (SELECT COUNT(cat5_id) FROM `cat5`),
            (SELECT COUNT(cat6_id) FROM `cat6`),
            (SELECT COUNT(cat7_id) FROM `cat7`)';

$stmt = $db->prepare($sql);
$stmt->execute();
$stmt->bind_result($var1, $var2, $var3, $var4, $var5, $var6, $var7, $var8);
$stmt->fetch();
$stmt->free_result();
$stmt->close();
$sql  = 'SELECT COUNT(cat1_id) as `cat1` FROM `cat1`;';
$sql .= 'SELECT COUNT(cat2_id) as `cat2` FROM `cat2`;';
$sql .= 'SELECT COUNT(cat2_id) as `cat2_b` FROM `cat2` WHERE `date` >= DATE(NOW());';
$sql .= 'SELECT COUNT(cat3_id) as `cat3` FROM `cat3`;';
$sql .= 'SELECT COUNT(cat4_id) as `cat4` FROM `cat4`;';
$sql .= 'SELECT COUNT(cat5_id) as `cat5` FROM `cat5`;';
$sql .= 'SELECT COUNT(cat6_id) as `cat6` FROM `cat6`;';
$sql .= 'SELECT COUNT(cat7_id) as `cat7` FROM `cat7`;';

if ($db->multi_query($sql))
{ 
    do
    {
        if ($stmt = $db->store_result())
        {
            while ($row = $stmt->fetch_assoc())
            {
                foreach ($row as $key => $value)
                {
                    $count[$key] = $value;
                }
            }
            $stmt->free_result();
        }
    } while ($db->more_results() && $db->next_result());
}
虽然单独的查询如下所示(x 8):


因此,与此类查询(如统计、计数器等)相关的更快或“更好的样式”是什么?

更好的方法是只使用一个查询,因为与数据库只有一个连接,而不是,如果使用多个查询,则与数据库有多个连接,此过程包括:连接和断开连接,这会更慢。

如果可能的话,我倾向于将查询放在FROM而不是SELECT中。在本例中,它需要表之间的交叉联接:

select c1.val, c2.val . . .
from (select count(cat1_id) as val from cat1) c1 cross join
     (select count(cat2_id as val from cat2) c2 cross join
     . . .
性能应该是相同的。但是,cat2表具有以下优点:

select c1.val, c2.val, c2.valnow, . . .
from (select count(cat1_id) as val from cat1) c1 cross join
     (select count(cat2_id) as val
             count(case when date >= date(now()) then cat2_id end)
      from cat2
     ) c2 cross join
     . . .
通过不必扫描两次表以获得两个值,您可以在这里获得真正的节省。当您意识到可能需要修改查询以返回多个值时,这也会有所帮助


我相信交叉连接和select中的select将具有相同的性能特征。要真正确定这一点,唯一的方法就是测试不同的版本。

下面是一个使用我的一个DBs的示例。在这里使用预先准备好的声明不会给你带来任何好处。这个多查询实际上只对D/B引擎执行一个RPC。所有其他调用都是PHP运行时系统的本地调用

$db = new mysqli('localhost', 'user', 'password', 'blog');
$table  = explode( ' ', 'articles banned comments config language members messages photo_albums photos');
foreach( $table as $t ) {
   $sql[] = "select count(*) as count from blog_$t";
}
if ($db->multi_query( implode(';',$sql) )) {
  foreach( $table as $t ) {
    if  ( ($rs  = $db->store_result() ) &&
          ($row = $rs->fetch_row()    ) ) {
       $result[$t] = $row[0];
       $rs->free();
       $db->next_result(); // you must execute one per result set
    }
  }
}
$db->close();
var_dump( $result );
出于兴趣,我做了一个
strace
,相关的四行是

16:54:09.894296 write(4, "\211\1\0\0\3select count(*) as count fr"..., 397) = 397
16:54:09.895264 read(4, "\1\0\0\1\1\33\0\0\2\3def\0\0\0\5count\0\f?\0\25\0\0\0\10\201"..., 16384) = 544
16:54:09.896090 write(4, "\1\0\0\0\1", 5) = 5
16:54:09.896192 shutdown(4, 2 /* send and receive */) = 0

在查询和对MySQLd进程的响应之间有大约1毫秒的时间(这是因为这是在localhost上,并且结果在其查询缓存中,顺便说一句)。。0.8毫秒后,执行DB关闭。关于TerryE的例子和使用multi_query(!)的建议,我查看了手册并根据需要更改了脚本。。最后,我得到了一个如下的解决方案:

$sql = 'SELECT
            (SELECT COUNT(cat1_id) FROM `cat1`),
            (SELECT COUNT(cat2_id) FROM `cat2`),
            (SELECT COUNT(cat2_id) FROM `cat2` WHERE `date` >= DATE(NOW())),
            (SELECT COUNT(cat3_id) FROM `cat3`),
            (SELECT COUNT(cat4_id) FROM `cat4`),
            (SELECT COUNT(cat5_id) FROM `cat5`),
            (SELECT COUNT(cat6_id) FROM `cat6`),
            (SELECT COUNT(cat7_id) FROM `cat7`)';

$stmt = $db->prepare($sql);
$stmt->execute();
$stmt->bind_result($var1, $var2, $var3, $var4, $var5, $var6, $var7, $var8);
$stmt->fetch();
$stmt->free_result();
$stmt->close();
$sql  = 'SELECT COUNT(cat1_id) as `cat1` FROM `cat1`;';
$sql .= 'SELECT COUNT(cat2_id) as `cat2` FROM `cat2`;';
$sql .= 'SELECT COUNT(cat2_id) as `cat2_b` FROM `cat2` WHERE `date` >= DATE(NOW());';
$sql .= 'SELECT COUNT(cat3_id) as `cat3` FROM `cat3`;';
$sql .= 'SELECT COUNT(cat4_id) as `cat4` FROM `cat4`;';
$sql .= 'SELECT COUNT(cat5_id) as `cat5` FROM `cat5`;';
$sql .= 'SELECT COUNT(cat6_id) as `cat6` FROM `cat6`;';
$sql .= 'SELECT COUNT(cat7_id) as `cat7` FROM `cat7`;';

if ($db->multi_query($sql))
{ 
    do
    {
        if ($stmt = $db->store_result())
        {
            while ($row = $stmt->fetch_assoc())
            {
                foreach ($row as $key => $value)
                {
                    $count[$key] = $value;
                }
            }
            $stmt->free_result();
        }
    } while ($db->more_results() && $db->next_result());
}
TerryE的例子有些不同,但结果是一样的。我知道开头有7行几乎相同,但只要我需要WHERE子句或其他东西,我就喜欢这个解决方案,而不是需要手动添加查询或使用异常的foreach循环,如果{…}


据我所知,我的解决方案应该没有问题,或者我遗漏了什么?

我明白了。因此,您认为select或from中的查询在性能上不会有真正的差异,尽管我需要在同一个表中计数两次会有好处吗?您是否同意levi的说法,多个查询速度较慢?@qlt。Levi就通信开销提出了一个非常重要的观点。有时,您确实希望有多个查询,因为优化器工作不正常。MySQL优化器有时让我感到惊讶,但很难看出交叉连接与select中的多个select有多大不同。当然,为了确保这一点,没有什么比测试查询时间更好的了。由于我通常在select中避免select,所以我没有研究过这一点。您使用的是MySQLi,所以为什么不保持简单并对6个select执行多集查询呢。这具有(A)的RPC开销和(B)的简单性。。。你的意思是像$db->multi_query($sql)这样没有准备好的语句吗?我以前从来没有用过,但这是一个非常好的建议,我甚至还没有想过!我刚刚发布了一个示例,向您展示了如何做到这一点:-)请注意,您必须清除返回堆栈中所有挂起的R,否则MySQLi接口将陷入困境。:-)我刚刚测试了你的脚本,到目前为止它还可以工作:)我记得我尝试了手册中的multi_查询示例(使用
do{…}while
),但由于某种原因它没有工作。但是有两点:1)当没有下一个结果时,我收到了一个通知,所以我将其更改为
if($db->more_results()){$db->next_result();}
,并且似乎有效。。还是有更好的解决方案?2) 您的示例使用相同的查询x次,但在我的示例中,我得到了一个WHERE子句,需要单独添加。。我在第一个foreach中使用IF实现了这一点,但我仍在寻找是否有其他方法来实现这一点:)