【问题标题】: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,这样我们就不必猜测数据类型、索引和约束。帮助我们帮助您!

标签: php mysql query-optimization


【解决方案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 检查执行速度;这可能会查明有问题的函数/查询,以便我们为您提供更好的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-13
  • 1970-01-01
相关资源
最近更新 更多