优化Mysql搜索查询

时间:2013-08-03 01:29:46

标签: php mysql

我有一个搜索查询,我正在尝试优化。我是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}%'

出于某种原因,它的运行速度非常慢,我不确定是否会开始减肥。

4 个答案:

答案 0 :(得分:3)

即使你在first_namelast_name上有适当的索引,一旦你发现它们就没有意义了。

我获得了良好结果的方法(跨越数百万条记录)是应用程序逻辑和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比较开始时使用通配符将停止使用任何索引。

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

答案 1 :(得分:2)

名称是人们期望能够搜索的,并且有效地进行搜索。

跳过连接和连接;在表格中维护一个正确的“全名”列。对它进行索引,甚至部分匹配也可以通过索引扫描有效地运行。目前,您通过给出计算表达式而无法优化,从而在查询引擎的面​​上随地吐痰。

一旦您可以匹配FULL_NAME中的部分内容,您就不需要在FIRST或LAST上使用单独的OR子句。 (顺便说一句,OR是低效的。)

正如Michael所说,正确地编写查询结构。 CUSTOMER最简单的是连接,而不是子查询。

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

答案 2 :(得分:2)

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

每次运行查询时,

创建customers表的副本

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

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为空字符串时,完整扫描可能是最快的。

如果searchtext与任何行都不匹配,那么完整的索引扫描可能会更快。

你真的要测试性能。

(可能你已经在其他两个表的id列上有了索引,因为id列可能是这些表的PRIMARY KEY。如果不是这样,那么你肯定希望有一个在这些表上定义的索引,以id作为前导列,以提高连接性能。)

答案 3 :(得分:1)

EXPLAIN放在前面,然后评估结果。您将寻找非常大的字段索引,导致查询需要更长时间。通过制作一些新密钥来优化这些索引。