需要你帮助优化一个mysql查询。让我们以简单的表格为例。
CREATE TABLE `Modules` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`moduleName` varchar(100) NOT NULL,
`menuName` varchar(255) NOT NULL,
PRIMARY KEY (`ID`),
KEY `moduleName` (`moduleName`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
让我们填写一些数据:
INSERT INTO `Modules` (`moduleName` ,`menuName`)
VALUES
('abc1', 'name1'),
('abc', 'name2'),
('ddf', 'name3'),
('ccc', 'name4'),
('fer', 'name5');
还有一些示例字符串。设为abc_def
;
传统上我们试图找到包含搜索字符串的所有行。
相反,我的任务是找到输入字符串中包含moduleName
的所有行。现在我有以下查询来获得所需的结果:
SELECT `moduleName` ,`menuName`
FROM `Modules`
WHERE 'abc_def' LIKE(CONCAT(`moduleName`,'%'))
这将返回
moduleName | menuName
---------------------------
abc | name2
问题是,此查询未使用索引。
有没有办法强迫它使用一个?
答案 0 :(得分:11)
您似乎误解了什么是索引以及它如何有助于加快查询速度。
让我们来看看你的moduleName
索引是什么。它基本上是从moduleName到ID的映射的排序列表。你在选择什么?
SELECT moduleName, menuName
FROM Modules
WHERE 'abc_def' LIKE CONCAT(moduleName,'%');
那就是你想要每行有两个字段,这些字段与moduleName字段的某种映射值有某种关系。怎么能索引帮助你?没有完全匹配,并且没有办法利用我们对moduleNames进行排序的事实。
您需要利用索引来检查条件是否完全匹配:
SELECT moduleName, menuName
FROM Modules
WHERE moduleName = LEFT('abc_def', LENGTH(moduleName));
现在我们确实有一个完全匹配,但由于条件的正确部分也取决于moduleName,因此将检查每一行的这个条件。因为在他的情况下,MySQL无法预测将匹配多少行,但它可以预测它将需要randon磁盘访问来获取每个匹配行的menuNames,MySQL将不会使用该索引。
所以你基本上有两种方法:
(moduleName, menuName)
,然后查询的所有结果将直接从索引中获取(即从内存中获取)。方法#2(参见SQLfiddle)将通过简单查询获得索引命中,并且应该在更大的表上提供更好的性能。在小桌子上,我(,即lserni - 见评论)并不认为这是值得的。
答案 1 :(得分:7)
你实际上正在对该领域进行正则表达式,因此没有密钥可以正常工作。但是,在您的示例中,您可以使其更有效,因为匹配的每个moduleName必须小于或等于'abc_def',因此您可以添加:
and moduleName <= 'abc_def'
我能想到的唯一另一种选择是:
where modleName in ('a','ab','abc','abc_','abc_d','abc_de','abc_def')
不漂亮。
答案 2 :(得分:4)
尝试在问题中添加index hint。
SELECT `moduleName` ,`menuName`
FROM `Modules` USE INDEX (col1_index,col2_index)
WHERE 'abc_def' LIKE(CONCAT(`moduleName`,'%'))
答案 3 :(得分:4)
因为,你的dtabase引擎是“InnoDB” InnoDB中默认的所有用户数据都存储在包含B树索引的页面中
B-tree are good for following lookups:
● Exact full value (= xxx)
● Range of values (BETWEEN xx AND yy)
● Column prefix (LIKE 'xx%')
● Leftmost prefix
因此,对于您的查询,而不是使用索引或某些内容进行优化, 我们可以想到加快查询。
您可以通过创建覆盖索引来加快查询速度。
覆盖索引是指all fields selected in a query are covered by an index
的情况,在这种情况下是InnoDB(不是MyISAM)will never read the data in the table, but only use the data in the index
,significantly speeding up the select
。
请注意,在InnoDB中,主键包含在所有二级索引中,因此所有二级索引都是复合索引。
这意味着如果您在InnoDB上运行以下查询:
SELECT `moduleName` ,`menuName`
FROM `Modules1`
WHERE 'abc_def' LIKE(CONCAT(`moduleName`,'%'))
MySQL will always use a covering index and will not access the actual table
To believe, go to **Explain**
What does Explain statement mean?
table:
表示输出受影响的表。
type:
向我们显示正在使用的联接类型。从最好到最差
类型是:system,const,eq_ref,ref,range,index,all
possible_keys:
表示MySQL可以选择哪些索引来查找此表中的行
key:
表示MySQL实际决定使用的密钥(索引)。如果MySQL决定使用其中一个possible_keys索引来查找行,那么该索引将被列为键值。
key_len:
这是所用密钥的长度。越短越好。
ref:
使用哪个列(或常量)
rows:
MySQL认为必须检查以执行查询的行数。
extra Extra info:
在这里看到的不好的是“使用临时”和“使用filesort”
我有1,990行。
我的实验:
我会推荐Isern的where子句
的解决方案 case 1) no indexes
explain select `moduleName` ,`menuName` FROM `Modules1` WHERE moduleName = SUBSTRING('abc_def', 1, LENGTH(moduleName));
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | Modules | ALL | NULL | NULL | NULL | NULL | 2156 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
创建覆盖索引的方法
case 2) ALTER TABLE `test`.`Modules1` ADD index `mod_name` (`moduleName`)
explain select `moduleName` ,`menuName` FROM `Modules1` WHERE moduleName = SUBSTRING('abc_def', 1, LENGTH(moduleName));
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | Modules | ALL | NULL | NULL | NULL | NULL | 2156 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
这里显示正在使用的索引。请参阅列:键,额外
case 3) ALTER TABLE `test`.`Modules1` DROP INDEX `mod_name` ,
ADD INDEX `mod_name` ( `moduleName` , `menuName` )
explain select `moduleName` ,`menuName` FROM `Modules1` WHERE moduleName = SUBSTRING('abc_def', 1, LENGTH(moduleName));
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
| 1 | SIMPLE | Modules | index | NULL | mod_name | 1069 | NULL | 2066 | Using where; Using index |
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
case 4) ALTER TABLE `test`.`Modules1` DROP INDEX `mod_name` ,
ADD INDEX `mod_name` ( `ID` , `moduleName` , `menuName` )
explain select `moduleName` ,`menuName` FROM `Modules1` WHERE moduleName = SUBSTRING('abc_def', 1, LENGTH(moduleName));
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
| 1 | SIMPLE | Modules | index | NULL | mod_name | 1073 | NULL | 2061 | Using where; Using index |
+----+-------------+----------+-------+---------------+----------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
编辑:
use where moduleName regexp "^(a|ab|abc|abc_|abc_d|abc_de|abc_def)$";
in place of substring()
答案 4 :(得分:3)
DECLARE @SEARCHING_TEXT AS VARCHAR(500)
SET @SEARCHING_TEXT ='ab'
SELECT'modumentName','menuName'FROM [MODULES] WHERE FREETEXT (MODULENAME,@ SEARCHING_TEXT);
答案 5 :(得分:3)
我不确定这是一个不错的查询,但它使用了索引:
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 7) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 6) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 5) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 4) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 3) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 2) = `moduleName`
UNION ALL
SELECT `moduleName` ,`menuName`
FROM `Modules` WHERE LEFT('abc_def', 1) = `moduleName`
一般解决方案
这是一个通用的解决方案,使用动态查询:
SET @search='abc_def';
SELECT
CONCAT(
'SELECT `moduleName` ,`menuName` FROM `Modules` WHERE ',
GROUP_CONCAT(
CONCAT(
'moduleName=\'',
LEFT(@search, ln),
'\'') SEPARATOR ' OR ')
)
FROM
(SELECT DISTINCT LENGTH(moduleName) ln
FROM Modules
WHERE LENGTH(moduleName)<=LENGTH(@search)) s
INTO @sql;
这将创建一个带有条件WHERE moduleName='abc' OR moduleName='abc_' OR ...
的SQL查询的字符串,它应该能够快速创建字符串(如果没有,可以使用临时索引表进行大量改进)数字从1到字符串的最大允许长度,例如小提琴中的例子)。然后你可以执行查询:
PREPARE stmt FROM @sql;
EXECUTE stmt;
请参阅小提琴here。
答案 6 :(得分:3)
我的回答可能更复杂
alter table Modules add column name_index int
alter table Modules add index name_integer_index(name_index);
当您插入模块表时,您计算moduleName的int值,类似于select ascii('a')
运行查询时,只需运行
即可SELECT `moduleName`, `menuName`
FROM `Modules`
WHERE name_index >
(select ascii('a')) and name_index < (select ascii('abc_def'))
它将使用name_integr_index
答案 7 :(得分:3)
类似于fthiella的建议,但更灵活(因为它可以轻松应对更长的字符串): -
SELECT DISTINCT `moduleName` ,`menuName`
FROM `Modules`
CROSS JOIN (SELECT a.i + b.i * 10 + c.i * 100 + 1 AS anInt FROM integers a, integers b, integers c) Sub1
WHERE LEFT('abc_def', Sub1.anInt) = `moduleName`
这个(作为类型)处理长达1000个字符的字符串,但比fthiellas解决方案慢。可以很容易地减少长达100个字符的字符串,此时它似乎比fthiellas解决方案略快。
检查它的长度确实加快了一点: -
SELECT SQL_NO_CACHE DISTINCT `moduleName` ,`menuName`
FROM `Modules`
INNER JOIN (SELECT a.i + b.i * 10 + c.i * 100 + 1 AS anInt FROM integers a, integers b, integers c ) Sub1
ON Sub1.anInt <= LENGTH('abc_def') AND Sub1.anInt <= LENGTH(`moduleName`)
WHERE LEFT('abc_def', Sub1.anInt) = `moduleName`
或稍作修改,以便从子选择中带回可能的子串: -
SELECT SQL_NO_CACHE DISTINCT `moduleName` ,`menuName`
FROM `Modules`
CROSS JOIN (SELECT DISTINCT LEFT('abc_def', a.i + b.i * 10 + c.i * 100 + 1) AS aStart FROM integers a, integers b, integers c WHERE( a.i + b.i * 10 + c.i * 100 + 1) <= LENGTH('abc_def')) Sub1
WHERE aStart = `moduleName`
请注意,这些解决方案取决于具有单个列的整数表和值为0到9的行。
答案 8 :(得分:3)
类似查询不使用索引...但您也可以定义一个全文索引来搜索这样的字符串。但是innodb引擎不支持它,只有myisam支持它。
答案 9 :(得分:3)
将索引键添加到moduleName 检查http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html B树索引特征更多信息
不确定为什么使用LIKE,最好避免使用LIKE。我的建议是将所有行保存在JSON中,然后对其执行AJAX搜索。
答案 10 :(得分:3)
(前一部分答案已删除 - 请参阅新手的答案,但答案相同,但更好)。
newtover的方法#2(请参阅SQLfiddle )会通过简单的查询获得索引,并且应该在较长的表格上提供更好的表现:
SELECT `moduleName`, `menuName`
FROM `Modules`
WHERE moduleName = LEFT('abc_def', LENGTH(moduleName));
如果您需要来自众多专栏的数据(而不只是menuName
),即如果Modules
更大也更长,那么您可能会更好将moduleName
移动到仅包含ID
,moduleName
及其长度的查找表中(以保存一个函数调用)。
所需的实际额外空间很小,如果moduleName
的基数较低,即很少moduleName
次重复menuName
次,您实际上可能最终节省可观的空间。
新架构将是:
moduleName_id integer, keys to Lookup.id
...all the fields in Modules except moduleName...
Lookup table
id primary key
moduleName varchar
moduleLength integer
和查询:
SELECT `Lookup`.`moduleName`,`menuName`
FROM `Modules` INNER JOIN `Lookup`
ON (`Modules`.`moduleName_id` = Lookup.id)
WHERE `Lookup`.`moduleName` = LEFT('abc_def',
`Lookup`.`moduleLength`);
此SQLfiddle从您的架构开始并修改它以实现上述目标。速度和存储空间的改进很大程度上取决于您在表中添加的数据。我故意让自己处于最佳状态(模块中的许多短字段,每个menuName
平均有一百moduleName
个},并且能够节省大约30%的存储空间;搜索性能只有3倍左右,并且可能受到I / O缓存的影响,所以除非有人进行更彻底的测试,否则我会把它留在“可观的空间并节省时间”。
另一方面,在小而简单的桌子和相同数量的菜单和模块(即1:1)上,存在轻微的存储损失,没有明显的速度增益。在这种情况下,所涉及的空间和时间将非常小,因此,尽管增加了复杂性,上面的“规范化”形式可能仍然是可行的方式。
答案 11 :(得分:0)
我们可以用一个函数本身instead
实现2个函数作为SUBSTRING('abcdef',1,LENGTH(moduleName))
where locate(moduleName, 'abc_def');