[英]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_name
和last_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
)和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优化器提供一些它实际上可以有效索引和使用的东西,并且几乎可以肯定会给您带来不错的性能。
查询性能的一个大问题是内联视图(别名为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.