14

需要你的帮助来优化一个 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

问题是,这个查询没有使用索引。

有没有办法强迫它使用一个?

4

12 回答 12

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 无法预测将匹配多少行,但它可以预测它将需要随机磁盘访问来获取每个匹配行的 menuNames,因此 MySQL 不会使用索引。

所以你基本上有两种方法:

  1. 如果您知道该条件显着缩小了匹配行的数量,那么您可以强制索引
  2. 另一种选择是将索引扩展到覆盖复合索引(moduleName, menuName),然后将直接从索引中获取所有查询结果(即从内存中)。

方法 #2(请参阅SQLfiddle)将通过简单的查询为您提供索引命中,并且应该在更大的表上提供更好的性能。在小桌子上,我(即 lserni - 见评论)认为这不值得付出努力。

于 2013-04-02T22:17:26.140 回答
7

你实际上是在现场做一个正则表达式,所以没有键可以工作。但是,在您的示例中,您可以提高效率,因为每个匹配的模块名称必须小于或等于“abc_def”,因此您可以添加:

and moduleName <= 'abc_def'

我能想到的唯一其他选择是:

where modleName in ('a','ab','abc','abc_','abc_d','abc_de','abc_def')

不漂亮。

于 2013-03-26T12:59:40.433 回答
4

尝试为您的问题添加索引提示。

SELECT `moduleName` ,`menuName` 
FROM `Modules` USE INDEX (col1_index,col2_index) 
WHERE 'abc_def' LIKE(CONCAT(`moduleName`,'%'))
于 2013-03-26T12:58:06.717 回答
4

因为,您的数据库引擎是“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:在这里看到的不好的是“使用临时”和“使用文件排序”

我有 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 |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+

在这里,它显示正在使用的索引。见栏目:key、Extra

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()
于 2013-04-04T10:22:24.577 回答
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

于 2013-04-01T03:11:59.780 回答
3

将索引键添加到 moduleName 检查http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html B-Tree Index Characteristics 了解更多信息

不知道你为什么使用 LIKE,最好避免它。我的建议是让所有行将其保存在 JSON 中,然后对其执行 AJAX 搜索。

于 2013-04-04T02:08:05.027 回答
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;

这将使用具有条件的 SQL 查询创建一个字符串,WHERE moduleName='abc' OR moduleName='abc_' OR ...并且由于索引,它应该能够快速创建字符串(如果没有,可以使用数字从 1 到允许的最大值的临时索引表来改进它您的字符串的长度,给出的小提琴示例)。然后你可以执行查询:

PREPARE stmt FROM @sql;
EXECUTE stmt;

在此处查看小提琴。

于 2013-03-29T15:26:30.007 回答
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 的行的整数表。

于 2013-04-02T13:50:01.243 回答
3

like 查询不使用索引...但是您也可以定义一个全文索引来搜索这样的字符串。但是innodb引擎不支持它,只有myisam支持它。

于 2013-04-03T23:28:01.247 回答
3

(答案的前一部分已删除 - 请参阅 newtover 的答案,该答案相同,但更好)

newtover 的方法 #2(请参阅SQLfiddle)将通过简单的查询为您提供索引命中,并且应该在较长的表上提供更好的性能:

SELECT `moduleName`, `menuName` 
FROM `Modules` 
WHERE moduleName = LEFT('abc_def', LENGTH(moduleName));

如果您需要来自很多列的数据(而不是仅menuName),即如果Modules更大和更长,则可以通过移动moduleName到仅包含 a ID、 themoduleName及其长度的查找表来为您提供更好的服务(以保存一个函数调用) .

实际需要的额外空间很小,如果moduleName基数低,即,你有几个moduleNames 沿着很多 s 重复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从您的架构开始并对其进行修改以实现上述目的。速度和存储空间的改进很大程度上取决于您在表中放入的数据。我故意让自己处于最佳状态(Modules 中有很多短字段,平均menuName每个 100 s moduleName)并且能够节省大约 30% 的存储空间;搜索性能只有大约 3 倍的速度,并且可能受到 I/O 缓存的影响,因此除非有人进行更彻底的测试,否则我会将其保留为“可以节省可观的空间和时间”。

另一方面,在小型、简单的表格和相同数量的菜单和模块(即 1:1)上,将有轻微的存储损失,而没有明显的速度增益。然而,在这种情况下,所涉及的空间和时间将非常小,因此尽管增加了复杂性,但上述更“规范化”的形式可能仍然是可行的方法。

于 2013-04-04T11:44:44.683 回答
3

将@SEARCHING_TEXT 声明为 VARCHAR(500)

SET @SEARCHING_TEXT = 'ab'

SELECT 'moduleName' ,'menuName' FROM [MODULES] WHERE FREETEXT (MODULENAME, @SEARCHING_TEXT );

于 2013-03-26T12:58:39.753 回答
0

我们可以用insteadSUBSTRING('abc_def', 1, LENGTH(moduleName)) 的 2 个函数的一个函数本身来实现

where locate(moduleName, 'abc_def');
于 2013-04-04T15:27:59.233 回答