MySQL索引VarChar


10

我试图索引blogentries数据库以获得更好的性能,但是发现了一个问题。

结构如下:

CREATE TABLE IF NOT EXISTS `blogentries` (
  `id_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `title_id` varchar(100) COLLATE latin1_german2_ci NOT NULL,
  `entry_id` varchar(5000) COLLATE latin1_german2_ci NOT NULL,
  `date_id` int(11) NOT NULL,
  PRIMARY KEY (`id_id`)
)
ENGINE=MyISAM
DEFAULT CHARSET=latin1
COLLATE=latin1_german2_ci
AUTO_INCREMENT=271;

如下查询将正确使用索引:

EXPLAIN SELECT id_id,title_id FROM blogentries ORDER by id_id DESC
+ ---- + -------------- + ------------- + ------- + -------- ------- + --------- + --------- + ------ + ------ + -------- ----- +
| id | select_type | 桌子| 类型 可能的钥匙| 关键 key_len | 参考| 行| 额外|
+ ---- + -------------- + ------------- + ------- + -------- ------- + --------- + --------- + ------ + ------ + -------- ----- +
| 1 | 简单 博客条目| 索引| NULL | 主要| 114 | NULL | 126 | 使用索引
+ ---- + -------------- + ------------- + ------- + -------- ------- + --------- + --------- + ------ + ------ + -------- ----- +

但是,当我entry_idSELECT查询中添加时,它将使用文件排序

EXPLAIN SELECT id_id,title_id,entry_id FROM blogentries ORDER by id_id DESC
+ ---- + ------------- + ------------- + ------ + --------- ------ + ------ + --------- + ------ + ------ + ------------ ---- +
| id | select_type | 桌子| 类型 可能的钥匙| 关键 key_len | 参考| 行| 额外|
+ ---- + ------------- + ------------- + ------ + --------- ------ + ------ + --------- + ------ + ------ + ------------ ---- +
| 1 | 简单 博客条目| 全部| NULL | NULL | NULL | NULL | 126 | 使用文件排序|
+ ---- + ------------- + ------------- + ------ + --------- ------ + ------ + --------- + ------ + ------ + ------------ ---- +

我想知道为什么会这样,如何避免呢?是由于造成的VarChar,应该将其更改为其他内容吗?

我试图让我所有的查询都使用索引,因为我遇到了高值Handler_read_rndHandler_read_rnd_next值。

如果您需要任何其他信息,我也可以发布。


filesort表示它正在对磁盘执行排序。
柯米特(Kermit)2012年

尝试添加WHERE 1=1到第二个查询。
柯米特(Kermit)2012年

这是哪个版本的MySQL?您的排序缓冲区大小(SELECT @@sort_buffer_size)是多少?

@njk filesort是查询的“ ORDER BY”部分的结果

1
@TashPemhiwa不一定,请参见第一个语句。
柯米特(Kermit)2012年

Answers:


6

由于WHERE在任一查询中都没有子句,因此在两种情况下都将返回所有行,因此在这些示例中,我认为使用或不使用索引对性能的影响很小。


当然,MySQL应该使用索引ORDER BY吗?
eggyal 2012年

@eggyal如果内存太大,则不会。
柯米特(Kermit)2012年

@njk:这没有意义……它可以按顺序遍历索引,而无需将整个内容加载到内存中。结果将被排序而无需执行文件排序。
eggyal 2012年

@eggyal我会质疑的大小varchar(5000)
柯里特(Kermit)2012年

@njk:但是该列既不在索引中,也不在排序中使用。
eggyal 2012年

2

ORDER BY优化中所述

对于filesort不使用的慢查询,请尝试降低max_length_for_sort_data到适合触发的值filesort

Peter Zaitsev 在他的博客文章read_rnd_buffer_size到底是什么中解释了:

对我来说,这意味着从MySQL 4.1开始,在少数情况下使用此选项-如果检索的字段很少(少于max_length_for_sort_data),则数据应存储在排序缓冲区和排序文件中,因此如果选定的列,则不需要read_rnd_buffer很长,因此它们比max_length_for_sort_data长,这通常意味着其中有一些TEXT / BLOB列。但是,如果有大量列或使用了很长的VARCHAR列,则将使用此方法–仅需要几个UTF8 VARCHAR(255)即可创建比其静态表示中的max_length_for_sort_data长的行。

这表明这max_length_for_sort_data是对所选择的列的总大小的限制,超过该限制filesort将使用a而不是基于索引的排序。

在您的情况下,选择entry_id(5002字节)将使总大小超过此变量的1KiB默认值,因此filesort将使用此大小。要将限制提高到8KiB,您可以执行以下操作:

SET SESSION max_length_for_sort_data = 8192;

我有一个与此表非常相似的表,并且此设置似乎不会触发文件排序的使用中的任何更改。

@muffinista:很有意思。我想根据@RolandoMySQLDBA的答案,它可能与其他一些缓冲区设置有关。
eggyal 2012年

2

您在这里得到了很多有趣的答复,但是没人能确切回答这个问题-为什么会这样?据我了解,当SELECT查询包含MySQL中的可变长度数据,并且没有与所有请求的列匹配的索引时,它将始终使用文件排序。数据的大小在这里并不十分相关。很难在MySQL文档中找到此问题的直接答案,但是这是一篇不错的博客文章,其中有人遇到与您的问题非常相似的问题。

另请参阅:优化MySQL查询的10条技巧(不要太糟了)

因此,如果可行的是在entry_id上建立索引,则可以添加它并全部设置。但是我怀疑这是一个选择,那么该怎么办?

您是否应该对此做任何事情是一个单独的问题。重要的是要知道'filesort'在MySQL中的命名很差 -它实际上只是用于对该特定查询进行排序的算法的名称,并且在许多情况下,排序实际上会在内存中进行。如果您不希望此表增长太多,则可能没什么大不了的。

另一方面,如果该表中将包含一百万行,则可能会有问题。如果您需要支持该表上的查询分页,则此处可能存在一个非常严重的性能问题。在这种情况下,将可变长度数据划分到一个新表中,并执行JOIN检索它是一个有效的优化考虑。

这是关于SO的其他几个答案,涉及这个问题:


OP的第一个查询“ 在MySQL中包含可变长度的数据,并且没有与所有请求的列匹配的索引 ”,但filesort在这种情况下显然没有使用。我还认为,即使仅对内存中的一个小表进行排序也可能会导致无法接受的性能下降:例如,如果查询执行过多(并且表发生了更改,则无法使用缓存)。
eggyal 2012年

我没有时间测试它,但是我想知道是否是由VARCHAR触发的,VARCHAR需要2个字节来存储dev.mysql.com/doc/refman/5.1/en/char中指定的长度。 html-因此第一个查询适合该限制,但第二个则不适合。

0

尝试WHERE在查询中添加一个子句。

即使ORDER BY与索引不完全匹配,也可以使用索引,只要索引的所有未使用部分和所有额外的ORDER BY列在WHERE子句中都是常量即可。在某些情况下,MySQL无法使用索引来解析ORDER BY,尽管它仍然使用索引来查找与WHERE子句匹配的行。

http://dev.mysql.com/doc/refman/5.0/en/order-by-optimization.html


但是在这种情况下,ORDER BY 确实与索引完全匹配,因此不需要WHERE子句。
eggyal 2012年

我在网站上的实际查询中有一个“ where”子句,所以我知道那不是文件排序的原因。我想知道是否使用varchar?

0

就我所知,varchar最多只能容纳8000个字节,大约4000个字符。因此,5000似乎超出了存储的限制,在这种情况下,可能是造成混乱的原因。

“ varchar [(n | max)]可变长度的非Unicode字符数据。n可以是1到8,000之间的值。max表示最大存储大小为2 ^ 31-1字节。存储大小为实际值输入的数据长度+ 2个字节。输入的数据长度可以为0个字符。varchar的SQL-2003同义词是char可变或字符可变。”

希望这能回答您的问题


The CHARand VARCHARTypes:“ 中所述,VARCHAR列中的值是可变长度的字符串。长度可以指定为0到255之间的值,在MySQL 5.0.3之前是0到65,535在5.0.3及更高版本中。有效VARCHARMySQL 5.0.3及更高版本中a的最大长度受最大行大小(65,535字节,在所有列之间共享)和所使用的字符集的
约束

0

您的表中只有126行。即使每一行的大小最大约为5KB,这也意味着从磁盘读取的总大小仅为600KB-这不是很多。坦率地说,它的数量很少,可能小于大多数现代磁盘驱动器的缓存大小。

现在,如果服务器需要检索您的数据来满足您的查询,则最昂贵的操作是从磁盘读取数据。但是,按照索引顺序读取它并非总是最快的方法,尤其是当数据量非常小时。

在您的情况下,将整个表数据作为单个块从磁盘读取到内存中(可能仅在一次磁盘读取操作或查找中),然后将其在RAM中排序以满足ORDER BY,这比磁盘的即时性要高得多。读取操作。如果服务器根据索引读取数据,则必须发出多达126次(糟糕!)读取操作,在同一数据文件中来回搜索多次。

换句话说,顺序扫描并不总是一件坏事,而mysql不一定是愚蠢的。如果尝试强制mysql使用该索引,则它的工作速度可能会比当前的顺序扫描慢。

并且,当不包括5KB字段时,它使用索引的原因是因为随后检索的数据未构成表中数据的99%。当您包含5KB字段时,现在查询必须读取99%的数据,并且随后读取整个内容并将其排序在内存中会更便宜。


听起来您好像在混淆如何避免全表扫描中的许多事情,这与满足JOIN条件和WHERE子句(而不是ORDER BY子句)中的索引使用有关。
eggyal 2012年

恰恰相反。在这种特殊情况下,全表扫描是一件好事,因为它比按索引顺序读取要快。

0

您正在使用哪个版本的MySQL?

在5.1中,我尝试设置您的方案并填充了一些虚拟数据。使用您提供的SQL,每次只能根据EXPLAIN获得表扫描。默认情况下,当您使用MYSQL命令时,即使在BY中使用主索引,MYSQL也会求助于文件排序。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.