MySQL:关于 INDEX 使用,LIKE 123 和 = 123 之间的区别

问题描述 投票:0回答:3

我在 select 语句上遇到奇怪的行为。

假设以下表结构包含数百万个条目:

CREATE TABLE `obj` (
  `obj__id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `obj__obj_type__id` int(10) unsigned DEFAULT NULL,
  `obj__title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `obj__const` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `obj__description` text COLLATE utf8_unicode_ci,
  `obj__created` datetime DEFAULT NULL,
  `obj__created_by` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `obj__updated` datetime DEFAULT NULL,
  `obj__updated_by` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `obj__property` int(10) unsigned DEFAULT '0',
  `obj__status` int(10) unsigned DEFAULT '1',
  `obj__sysid` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `obj__scantime` datetime DEFAULT NULL,
  `obj__imported` datetime DEFAULT NULL,
  `obj__hostname` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `obj__undeletable` int(1) unsigned NOT NULL DEFAULT '0',
  `obj__rt_cf__id` int(11) unsigned DEFAULT NULL,
  `obj__cmdb_status__id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`obj__id`),
  KEY `obj_FKIndex1` (`obj__obj_type__id`),
  KEY `obj_ibfk_2` (`obj__cmdb_status__id`),
  KEY `obj__sysid` (`obj__sysid`),
  KEY `obj__title` (`obj__title`),
  KEY `obj__const` (`obj__const`),
  KEY `obj__hostname` (`obj__hostname`),
  KEY `obj__status` (`obj__status`),
  KEY `obj__updated_by` (`obj__updated_by`)
) ENGINE=InnoDB AUTO_INCREMENT=7640131 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

一个非常简单的选择,具有两个条件,按 obj__title 排序,限制为 500,执行速度非常慢(500ms):

SELECT SQL_NO_CACHE * FROM obj WHERE (obj__status = 2) AND (obj__obj_type__id = 59) ORDER BY obj__title ASC LIMIT 0, 500;

如果没有“ORDER BY obj__title”,它的运行就像一个魅力(<1ms).

EXPLAIN SELECT 告诉我 MySQL 正在执行文件排序而不是使用 obj__title 索引

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  obj index_merge obj_FKIndex1,obj__status    obj_FKIndex1,obj__status    5,5 NULL    1336    Using intersect(obj_FKIndex1,obj__status); Using where; Using filesort

当我强制索引 obj__title 与 FORCE 或 USE INDEX 一起使用时,mysql 没有使用其他索引,从而再次导致性能非常差。看起来性能不佳与两个条件的组合和 order by 有关。

现在我花了几个小时研究优化这个查询,我想出了一个非常简单的改变:我将条件运算符从 = 交换为 LIKE。所以我的查询是:

EXPLAIN SELECT SQL_NO_CACHE * FROM obj WHERE (obj__status LIKE 2) AND (obj__obj_type__id LIKE 59) ORDER BY obj__title ASC LIMIT 0, 500;

这就是发生的事情..

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  obj index   obj_FKIndex1,obj__status    obj__title  768 NULL    500 Using where

查询性能更快:150ms

我对速度不太满意,但至少表现还不错。

但我真正想知道的是为什么 LIKE 使用索引,而 = 不使用索引?我在 MySQL 文档中没有找到任何相关提示。只有一些关于 LIKE 不区分大小写的注释,并且 LIKE 对于 VARCHARS > 255 或任何其他 CHAR 或 TEXT 字段的行为有点不同。没有任何关于它在整数字段上的行为的说明。

有人可以解释一下这种情况吗?任何可以加快查询速度的数据库设计或查询技巧也非常受欢迎!

mysql performance sql-order-by
3个回答
1
投票

对于此查询:

SELECT SQL_NO_CACHE *
FROM obj
WHERE (obj__status = 2) AND (obj__obj_type__id = 59)
ORDER BY obj__title ASC
LIMIT 0, 500;

最好的指数是

obj(obj__status, obj__obj_type__id, obj__title)

否则,我希望在两个

where
字段之一上建立索引。

但是,当您使用

like
时,您正在将数字与字符串进行比较。这通常会阻止使用索引。唯一可能的索引是
order by
,它恰好适用于您的情况。

但是,合适的索引应该有更好的性能。


1
投票

ORDER BY
必须先于
LIMIT
得到满足。如果存在大量行,并且 MySQL 执行 Extra 列中显示的排序操作(“使用文件排序”),则成本可能会很高。

MySQL 还可以通过使用前导列为

ORDER BY obj__title
的索引来满足
obj__title
的要求,而无需执行排序操作。这就是当您更改谓词时所看到的情况。 EXPLAIN 显示
obj__title
上的索引正在被使用,没有排序操作。但 MySQL 必须检查每一行,看看它是否满足谓词。

LIKE 谓词导致在字符串上下文中计算列,而不是数字。也就是说,MySQL 必须执行从整数到 varchar 的隐式转换。这会阻止 MySQL 使用索引来满足谓词。 MySQL 基本上被迫对表中的每一行进行转换,以便评估谓词。


为了第一个查询的最佳性能:

  SELECT SQL_NO_CACHE * 
    FROM obj 
   WHERE obj__status = 2
     AND obj__obj_type__id = 59
   ORDER BY obj__title ASC
   LIMIT 0, 500

您需要一个带有前导列的索引:

 .... ON obj (obj__status, obj__obj_type__id, obj__title)

然后,MySQL 可以通过使用单个索引来满足相等谓词的两个顺序。

请注意,这使得单列 obj__status 上的索引变得多余。任何使用

obj__status

 上的索引的查询都可以使用新索引。


1
投票
您的第一个选择需要这个复合索引。 (我冒昧地删除了“obj_”,它只会让 SQL 变得混乱。)

INDEX(type_id, status, title)

MySQL 很少在查询中使用多个索引;这个 3 列索引适合

WHERE status=(const) AND type_id=(const) ORDER BY title

。我看到它使用“索引相交”来尝试弥补缺乏合适的复合索引,但只是部分地。

也许优化器看着

LIKE

 并说“平底锅!我放弃使用 
numeric 比较,所以我们不要在 type_id 或 status 上使用索引。相反,让我们看看是否可以通过使用 INDEX(title)
 来避免文件排序”。而且它碰巧变得更好了。

还有一件事使得

that 文件排序的成本特别高。 “使用临时”和“文件排序”更喜欢通过内存表在 RAM 中完成所有操作。但有几件事可以防止这种情况发生。一种是获取 TEXT 字段,您可以这样做(SELECT *

,其中包括 
description TEXT
)。我怀疑优化器是否注意到了这一点。但时间似乎有。

有关索引的更多提示,请参阅

我的索引食谱。同时,仅对字符串使用 LIKE

,而不是数值。

© www.soinside.com 2019 - 2024. All rights reserved.