我在 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 字段的行为有点不同。没有任何关于它在整数字段上的行为的说明。
有人可以解释一下这种情况吗?任何可以加快查询速度的数据库设计或查询技巧也非常受欢迎!
对于此查询:
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
,它恰好适用于您的情况。
但是,合适的索引应该有更好的性能。
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
上的索引的查询都可以使用新索引。
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
,而不是数值。