让我的查询更短、更快、更好

Posted

技术标签:

【中文标题】让我的查询更短、更快、更好【英文标题】:Making my queries shorter, faster and better 【发布时间】:2017-11-19 16:50:09 【问题描述】:

我正在运行一个大型 php 脚本来获取适用于搜索查询等的数据库信息。问题是页面的加载时间是几秒钟(本地),而在远程数据库上最多为一分钟。

在优化方面我不是最好的,但我知道查询中的星号,但在这种情况下我需要它,因为我正在使用表中的所有字段。

请记住,它从中获取数据的表总共有超过 800k 行(我没有显示 800k 行,如代码中所示,我正在显示它WHERE = something

<?php

    $getPage = $mysqli->real_escape_string(@$_GET["page"]); 
    $getSearch = $mysqli->real_escape_string(@$_GET["search"]);
    $getUser = $mysqli->real_escape_string(@$_GET["user"]);

    if(isset($getPage))  $page  = $getPage;  else  $page = 1; ; 
    $start_from = ($page-1) * 12;

    $order = $mysqli->real_escape_string(@$_GET["ord"]); 

    $order_query = "item_name ASC";

    if($start_from >= 0) 
        if($getSearch)
        
            if($order)
            
                if($order == 1)
                
                    $order_query = "item_name ASC";
                

                if($order == 2)
                
                    $order_query = "item_name DESC";
                

                if($order == 3)
                
                    $order_query = "item_id ASC";
                

                if($order == 4)
                
                    $order_query = "item_id DESC";
                

                $shop = $mysqli->query("SELECT * FROM store_inventory WHERE (item_name LIKE '%".$getSearch."%' OR item_type LIKE '%".$getSearch."%' OR item_quality LIKE '%".$getSearch."%')  AND steamid = '".$getUser."' GROUP BY item_id ORDER BY ".$order_query." LIMIT $start_from, 28");
            
            else
            
                $shop = $mysqli->query("SELECT * FROM store_inventory WHERE (item_name LIKE '%".$getSearch."%' OR item_type LIKE '%".$getSearch."%' OR item_quality LIKE '%".$getSearch."%')  AND steamid = '".$getUser."' GROUP BY item_id ORDER BY item_name ASC LIMIT $start_from, 28");
            
        
        else 
        
            if($order)
            
                if($order == 1)
                
                    $order_query = "item_name ASC";
                

                if($order == 2)
                
                    $order_query = "item_name DESC";
                

                if($order == 3)
                
                    $order_query = "item_id ASC";
                

                if($order == 4)
                
                    $order_query = "item_id DESC";
                

                $shop = $mysqli->query("SELECT * FROM store_inventory WHERE steamid = '".$getUser."' GROUP BY item_id ORDER BY ".$order_query." LIMIT $start_from, 28");
            
            else
            
                $shop = $mysqli->query("SELECT * FROM store_inventory WHERE steamid = '".$getUser."' GROUP BY item_id ORDER BY item_name ASC LIMIT $start_from, 28");
            
        
     else 
        $start_from = 0;
        if($getSearch)
        
            if($order)
            
                if($order == 1)
                
                    $order_query = "item_name ASC";
                

                if($order == 2)
                
                    $order_query = "item_name DESC";
                

                if($order == 3)
                
                    $order_query = "item_id ASC";
                

                if($order == 4)
                
                    $order_query = "item_id DESC";
                

                $shop = $mysqli->query("SELECT * FROM store_inventory WHERE (item_name LIKE '%".$getSearch."%' OR item_type LIKE '%".$getSearch."%' OR item_quality LIKE '%".$getSearch."%')  AND steamid = '".$getUser."' GROUP BY item_id ORDER BY ".$order_query." LIMIT $start_from, 28");
            
            else
            
                $shop = $mysqli->query("SELECT * FROM store_inventory WHERE (item_name LIKE '%".$getSearch."%' OR item_type LIKE '%".$getSearch."%' OR item_quality LIKE '%".$getSearch."%')  AND steamid = '".$getUser."' GROUP BY item_id ORDER BY item_name ASC LIMIT $start_from, 28");
            
        
        else 
        
            if($order)
            
                if($order == 1)
                
                    $order_query = "item_name ASC";
                

                if($order == 2)
                
                    $order_query = "item_name DESC";
                

                if($order == 3)
                
                    $order_query = "item_id ASC";
                

                if($order == 4)
                
                    $order_query = "item_id DESC";
                

                $shop = $mysqli->query("SELECT * FROM store_inventory WHERE  steamid = '".$getUser."' GROUP BY item_id ORDER BY ".$order_query." LIMIT $start_from, 28");
            
            else
            
                $shop = $mysqli->query("SELECT * FROM store_inventory WHERE  steamid = '".$getUser."' GROUP BY item_id ORDER BY item_name ASC LIMIT $start_from, 28");
            
        
    

    $countarticles = $mysqli->query("SELECT COUNT(item_id) FROM store_inventory");
    $row = $countarticles->fetch_row();
    $total_records = $row[0]; 
    $total_pages = ceil($total_records / 28);

    $Pages[] = "";        
    for ($i = 1; $i <= $total_pages; $i++)  
        $Pages[] = "<li><a href='?page=".$i."'>".$i."</a></li>";
    

    while($fetch_market = $shop->fetch_array())
    
        $iInv[] = $fetch_market;
    

如何显着减少脚本/页面的加载时间?

【问题讨论】:

首先避免使用 select * 直到并且除非你需要整行 @DhavalChheda 正如我所说,我需要整行。 你有关于 steamid 的索引吗? 不,steamid 上没有索引 @P.Nick 当您请求查询优化帮助时,请在查询中为每个表添加SHOW CREATE TABLE,这样我们就不必猜测数据类型、索引和约束。帮助我们帮助您! 【参考方案1】:

这是您的选择查询。

SELECT * 
  FROM store_inventory
 WHERE (   item_name LIKE '%".$getSearch."%'
        OR item_type LIKE '%".$getSearch."%'
        OR item_quality LIKE '%".$getSearch."%'
       )
   AND steamid = '".$getUser."'
 GROUP BY item_id ORDER BY ".$order_query."  
 LIMIT $start_from, 28

相对而言,您编写此查询的方式保证非常慢。

为什么?

首先,您使用的是WHERE column LIKE '%value%'。这种过滤器表达式不可能被索引加速。为什么不?因为它必须查看列中的每个值以查看它是否与您的表达式匹配。另一方面,WHERE column LIKE 'value%' 没有前导 %,可以利用索引。你拥有它的方式需要一个全表扫描来执行你的过滤器。

其次,您使用了三个OR 表达式,每个表达式都带有性能不佳的LIKE 过滤器。这会将您的一张表扫描变成三张,将花费的时间增加三倍。

第三,你在这里误用了GROUP BY。 MySQL 可以让你摆脱各种奇怪的东西。阅读此内容:https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html 我会就如何解决此问题提出建议,但我猜不出您要做什么。

第四,您正在使用臭名昭著的SELECT * ... ORDER BY x LIMIT y 性能反模式。这对一堆乱七八糟的行进行了排序,只丢弃了除少数之外的所有行。

你能做些什么呢?首先,确保您在该steamid 列上有一个索引。如果它具有合理的选择性,那么通过减少需要扫描的行数将有很大帮助。

其次,尝试用复合索引替换您的steamid 索引

steamid, item_name, item_type, item_quality

可能(也可能不会)使您的LIKE 扫描效率更高。

第三,弄清楚你实际上想用GROUP BY完成什么,并尝试正确地做到这一点。

第四,尽量减轻ORDER BY的负担。这样的事情可能会有所帮助。

SELECT * FROM store_inventory
 WHERE id IN (SELECT id
                FROM store_inventory
               WHERE (    this
                       OR that
                       OR the_other_thing
                     )
                 AND steamid = something
               ORDER BY some_column
               LIMIT start, count
              )

这使得 MySQL 只需对 id 进行排序。它更快。

第五,查找 MySQL 的 FULLTEXT 搜索选项。它可能会用更有效的方法取代您的一系列LIKE 操作。

【讨论】:

感谢您的意见!调查一下!【参考方案2】:

使用 PHP 7,准备好的语句和函数!

为什么使用 PHP 7? 根据 [Anna Monus][1]

,PHP 7 比 PHP 5.6 快两倍

为什么要使用准备好的语句? PHP7 + MySQLi 提供更好的性能和安全性。但是,您的代码仍然容易受到 SQL 注入的影响。请注意,Prepared Statements 可能很慢。 只有在准备语句然后多次执行时,准备好的语句才会变得更快。 但是由于您使用的是 mysqli_real_escape_string,它会往返于数据库,只需将所有这些往返都替换为单个准备好的语句使您的代码更快。

为什么要使用函数 函数使您的代码更短、更易于维护,并且在大多数情况下执行得更快。因为你不断地重复你的代码。您可以使用函数来缩短它。

我知道这已经够长了,所以这是你的代码:

$getPage = $mysqli->real_escape_string(@$_GET["page"]); 
$getSearch = $mysqli->real_escape_string(@$_GET["search"]);
$getUser = $mysqli->real_escape_string(@$_GET["user"]);

if(isset($getPage))  $page  = $getPage;  else  $page = 1; ; 
$start_from = ($page-1) * 12;

$order = $mysqli->real_escape_string(@$_GET["ord"]); 

$order_query = "item_name ASC";

function order_item($order) 
    if($order == 1) 
        $order_query = "item_name ASC";
    
    if($order == 2) 
        $order_query = "item_name DESC";
    
    if($order == 3) 
        $order_query = "item_id ASC";
    
    if($order == 4) 
        $order_query = "item_id DESC";
    
return $order_query;


function shop_type($type, $orderBy) 
    $getSearch = "%$getSearch%";
    if ( $type = 'searched' ) 
        $add = "AND (item_name LIKE ? OR item_type LIKE ? OR item_quality LIKE ?)";
     else 
        $add = "";
    
    $stmt = $con->prepare("SELECT * FROM store_inventory 
        WHERE steamid = ? 
        ".$add."
        GROUP BY item_id
        ORDER BY ".$orderBy." ASC
        LIMIT $start_from, 28"); 
    $stmt->bind_param("is", $getUser, $getSearch);
    $stmt->execute();
    $result = $stmt->get_result();
    if($result->num_rows === 0) exit('No rows');
    while($row = $result->fetch_assoc()) 
      $arr[] = $row;
    
    return $arr;


function get_items() 
     if($getSearch) 
        if($order) 
            order_item($order);
            $shop = shop_type('searched',$order_query);
         else 
            $shop = shop_type('searched','item_name');
        
     else 
        if($order) 
            order_item($order);
            $shop = shop_type('unsearched',$order_query);
         else 
            $shop = shop_type('unsearched','item_name');
        
    


if($start_from >= 0) 
   get_items();
 else 
    $start_from = 0;
    get_items();


$countarticles = $mysqli->query("SELECT COUNT(item_id) FROM store_inventory");
$row = $countarticles->fetch_row();
$total_records = $row[0]; 
$total_pages = ceil($total_records / 28);

$Pages[] = "";        
for ($i = 1; $i <= $total_pages; $i++)  
    $Pages[] = "<li><a href='?page=".$i."'>".$i."</a></li>";


while($fetch_market = $shop->fetch_array())

    $iInv[] = $fetch_market;

另一个提示:

将函数放在一个单独的 PHP 文件中并使用以下方法调用它:

require "filename.php";

希望这会有所帮助!

【讨论】:

恕我直言,这是个好建议,但对问题没有回应。 很遗憾听到这个消息。但就我而言,我让他的代码 (1) 更短 (2) 更快,并希望 (3) 更好。 :D 有时会更快,有时会慢 10 倍。不知道是什么原因造成的。 我忘了说 LIKE 查询很慢。尝试使用 O. Jones 建议的其他查询。还可以尝试使用 microtime 检查执行速度;这可能会查明有问题的函数/查询,以便我们为您提供更好的答案。

以上是关于让我的查询更短、更快、更好的主要内容,如果未能解决你的问题,请参考以下文章

我正在寻找一种更短/更好的方法来从我的核心数据应用程序中的 NSSet 获取第一个对象

MongoDB:运行更快的查询,这更好 Pymongo 或 MongoEngine

关于非规范化。我怎样才能使这个查询更短或更好? (SQL SERVER 2000)[关闭]

如何让我的 javascript 动画更快?

我都有哪些选择可以让我的 ORDER BY 更快?

GDI +游戏引擎(我可以做些啥来让我的引擎运行得更快?)[重复]