繁体   English   中英

优化Mysql搜索查询

[英]Optimizing a Mysql search query

我有一个要优化的搜索查询。 我是mysql的新手,所以有人可以解释如何通过多个联接来优化这种查询吗?

SELECT cust.*, br.branchcode, br.branchname, over.branchcode override_branchcode, over.branchname override_branchname
                    FROM ( SELECT id, CONCAT( firstName, ' ', lastName ) fullName, firstname, lastname, phone1, phone2, mobile1, mobile2, unit, brgy, city, `primary`, override_pst
                    FROM sl_customers ) cust
                    LEFT JOIN sl_branches br ON cust.primary = br.id
                    LEFT JOIN sl_branches over ON cust.override_pst = over.id
                    WHERE fullName LIKE '{$searchtext}' OR firstname LIKE '%{$searchtext}%' OR lastname LIKE '%{$searchtext}%'

由于某种原因,它运行非常缓慢,我不确定是否开始减少脂肪。

即使您在first_namelast_name上具有适当的索引,一旦对它们进行CONCAT,它们也就毫无意义。

我取得了良好效果(跨越数百万条记录)的一种方法是将应用程序逻辑和SQL结合在一起。 假设全名总是与一个空格连接在一起,则可以按其空格将搜索文本(在应用程序级别)分开。 根据搜索文本中有多少空格,将确定您执行哪种查询。

首先,在两列之间添加索引,例如

ALTER TABLE `sl_customers` ADD INDEX idx_name_search (`first_name`,`last_name`);

然后,对所有以空格分隔的名称进行排列。 这是一个工作的php示例:

$search_text = 'millhouse van houten';
$conditions = '';

$parts = explode(' ', $search_text);

for($i=count($parts); $i>=0; $i--){
    $params[] = implode(' ', array_slice($parts, 0, $i)).'%'; //first name
    $params[] = implode(' ', array_slice($parts, $i)).'%'; //last anme

    $conditions .= '(`first_name` LIKE ? AND `last_name` LIKE ?) OR ';
}
$conditions = substr($conditions, 0, -4); //trim the last OR

$query = 'SELECT `first_name`, `last_name` FROM `customer` WHERE '.$conditions;

您最终得到如下查询:

SELECT `first_name`, `last_name` FROM `customer` WHERE 
(`first_name` LIKE ? AND `last_name` LIKE ?) OR 
(`first_name` LIKE ? AND `last_name` LIKE ?) OR 
(`first_name` LIKE ? AND `last_name` LIKE ?) OR 
(`first_name` LIKE ? AND `last_name` LIKE ?);

和类似的参数

[0] => millhouse van houten%
[1] => %
[2] => millhouse van%
[3] => houten%
[4] => millhouse%
[5] => van houten%
[6] => %
[7] => millhouse van houten%

这将搜索如下一组组合:

first_name             | last_name
-------------------------------------------------
millhouse van houten%  | %
millhouse van%         | houten%
millhouse%             | van houten%
%                      | millhouse van houten%

请记住,在大多数情况下,全名中实际上只有一个空格,因此比起我的示例,比较将更少。

您可能想玩通配符,但只要在( first_namelast_name )和last_name上保留索引,就始终可以有效地使用索引。 LIKE比较开始时使用通配符将停止使用任何索引。

很抱歉,冗长的答案-我只是想让这个想法尽可能清楚。

人们希望人们能够搜索并有效地进行名字。

跳过hokey串联并在表中维护适当的“全名”列。 在上面加上一个索引,甚至部分匹配也可以仅通过索引扫描就可以高效地运行。.目前,您为查询引擎提供了它永远无法优化的计算表达式,这让您不寒而栗。

一旦您可以匹配FULL_NAME中的部分字符,就不必在FIRST或LAST上使用单独的OR子句了。 (顺便说一下,OR效率很低。)

正如迈克尔所说,请正确编写查询的结构。 客户最简单地是联接,而不是子查询。

select CUST.*, BR.*, OVER.*            -- you can put in the specific columns.
from SL_CUSTOMERS CUST
join SL_BRANCHES BR on cust.primary = br.id
join SL_BRANCHES OVER on cust.override_pst = over.id
where CUST.FULL_NAME like '%{$searchtext}%';

为可怜的MySQL优化器提供一些它实际上可以有效索引和使用的东西,并且几乎可以肯定会给您带来不错的性能。

请参阅: http : //kristiannielsen.livejournal.com/802.html

查询性能的一个大问题是内联视图(别名为cust)。 MySQL称其为“派生表”,这是一个合适的名称,因为MySQL处理方式。 MySQL运行该查询,并将结果存储为临时MyISAM表,外部查询在该表上运行。 由于该视图查询中没有谓词,因此MySQL本质上是

每次运行查询时,都会创建一个 customer表的副本

从性能的角度来看,将搜索谓词从外部查询移动到内联视图的查询中会更好:

SELECT cust.*
     , br.branchcode
     , br.branchname
     , over.branchcode override_branchcode
     , over.branchname override_branchname
  FROM ( SELECT s.id
              , CONCAT(s.firstName,' ',s.lastName) fullName
              , s.firstname
              , s.lastname
              , s.phone1
              , s.phone2
              , s.mobile1
              , s.mobile2
              , s.unit
              , s.brgy
              , s.city
              , s.primary
              , s.override_pst
           FROM sl_customers s
          WHERE CONCAT(s.firstName,' ',s.lastName) LIKE '{$searchtext}'
             OR s.firstname LIKE '%{$searchtext}%'
             OR s.lastname  LIKE '%{$searchtext}%'
       ) cust
  LEFT 
  JOIN sl_branches br
    ON cust.primary = br.id
  LEFT
  JOIN sl_branches over 
    ON cust.override_pst = over.id

至少那可能会复制到“派生表”中的行数较少,尽管MySQL仍必须具体化该视图查询,然后在该查询上运行另一个查询。

为了更好地提高性能,我们可以完全消除内联视图:

SELECT s.id
     , CONCAT(s.firstName,' ',s.lastName) fullName
     , s.firstname
     , s.lastname
     , s.phone1
     , s.phone2
     , s.mobile1
     , s.mobile2
     , s.unit
     , s.brgy
     , s.city
     , s.primary
     , s.override_pst
     , br.branchcode
     , br.branchname
     , over.branchcode override_branchcode
     , over.branchname override_branchname
  FROM sl_customers s           
  LEFT 
  JOIN sl_branches br
    ON cust.primary = br.id
  LEFT
  JOIN sl_branches over 
    ON cust.override_pst = over.id
 WHERE CONCAT(s.firstName,' ',s.lastName) LIKE '{$searchtext}'
    OR s.firstname LIKE '%{$searchtext}%'
    OR s.lastname  LIKE '%{$searchtext}%'

就性能而言,下一个“大石头”是所有谓词都不是可预测的。 也就是说,MySQL不能对任何这些LIKE谓词使用范围扫描(因为在列的情况下,前导'%'。并且因为必须为每一行求值CONCAT表达式)。

全表扫描可能是通过此查询最快的方法。 您可能可以使MySQL充分利用索引ON cust (firstname,lastname)的索引,但是如果表和索引位于内存中,并且/或者仅需要表中的一小部分行,则不可能提高性能。被访问(由于通过索引查找访问基础表中的块的方式,并且随机读取速度较慢)。

当searchtext为空字符串时,全扫描可能最快。

如果搜索文本不匹配任何行,则全索引扫描可能会更快。

您实际上必须测试性能。

(您可能已经在其他两个表的id列上建立了索引,因为id列很可能是那些表的PRIMARY KEY。如果不是这种情况,那么您肯定想在这些表上定义一个索引, id作为最前面的列,以提高联接性能。)

将单词EXPLAIN放在其前面,然后评估结果。 您将要查找非常大的字段索引,这会使查询花费更长的时间。 通过创建一些新键来优化那些索引。

暂无
暂无

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

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