0

我有一个正在尝试优化的搜索查询。我对 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

4 回答 4

3

即使你在 and 上有正确的索引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_name, last_name) AND上留下索引last_name,您将始终有效地使用索引。在LIKE比较开始时使用通配符将停止使用任何索引。

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

于 2013-08-03T03:00:38.720 回答
2

名称是人们期望能够搜索并有效搜索的东西。

跳过 hokey 连接并在表中维护一个正确的“全名”列。在其上放置一个索引,即使是部分匹配也可以通过索引扫描有效地运行。目前,您通过给查询引擎提供它永远无法优化的计算表达式而在查询引擎的脸上吐痰。

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

正如迈克尔所说,正确编写查询的结构。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

于 2013-08-03T01:47:17.217 回答
2

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

每次运行查询时创建客户表的副本。

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

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 使用 index ON cust (firstname,lastname),但是如果表和索引在内存中,并且/或者只需要访问表中的一小部分行(由于通过索引查找访问基础表中的块的方式,随机读取速度较慢。)

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

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

你真的必须测试性能。

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

于 2013-08-03T02:13:19.410 回答
1

把这个词EXPLAIN放在它前面,然后评估结果。您将寻找非常大的字段索引,从而导致查询花费更长的时间。通过创建一些新键来优化这些索引。

于 2013-08-03T01:47:05.607 回答