我有一个搜索查询,我正在尝试优化。我是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}%'
出于某种原因,它的运行速度非常慢,我不确定是否会开始减肥。
答案 0 :(得分:3)
即使你在first_name
和last_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_name
,last_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优化器提供它可以实际索引的东西。有效地工作,几乎可以肯定会给你带来不错的表现。
答案 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
放在前面,然后评估结果。您将寻找非常大的字段索引,导致查询需要更长时间。通过制作一些新密钥来优化这些索引。