MySQL:索引只用在我select只有主键的时候
MySQL: index used only when I select only primary key
我在 MySQL 中有这个 table:
Table: city
Columns:
ID int(11) AI PK
Name char(35)
CountryCode char(3)
District char(20)
Population int(11)
我想在按 CountryCode 排序时使用索引,所以我在此列上创建了索引:
Index: CountryCode
Definition:
Type BTREE
Unique No
Columns CountryCode
为什么 EXPLAIN EXTENDED SELECT * FROM city ORDER BY CountryCode; 不使用索引:
| id: 1
| select type: SIMPLE
| table: city
| partitions: NULL
| type: ALL
| possible_keys: NULL
| key: NULL
| key_len: NULL
| ref: NULL
| rows: 4188
| filtered: 100.00
| Extra: Using filesort
and EXPLAIN EXTENDED SELECT id FROM world.city ORDER BY CountryCode; use index:
| id: 1
| select type: SIMPLE
| table: city
| partitions: NULL
| type: index
| possible_keys: NULL
| key: CountryCode
| key_len: 3
| ref: null
| rows: 4188
| filtered: 100.00
| Extra: Using index
如何更改此行为 - 即在第一个示例中添加索引用法?
没问题。第一个查询运行 faster by not using the index.
首先,让我们剖析第二个查询。显然你正在使用 InnoDB。这意味着 INDEX(country_code)
实际上是 INDEX(country_code, id)
。也就是说,PRIMARY KEY
被隐式附加到任何辅助键上。第二个查询只需要country_code
和id
,所以完全可以在索引中执行,如Using index
所示。它按顺序读取索引,并提供结果。效率很高。
现在,让我们看一下第一个查询。这次您要求 *
,而不仅仅是 id
。其余字段不在索引中。如果要使用索引(您可以使用 ... FROM city FORCE INDEX(CountryCode) ...
进行测试),则必须这样做:
- 扫描索引 -- 这会按顺序提供
ids
,但不会提供 *
的其余部分。
- 对于每个 id,获取其余
*
的数据。这是昂贵的,特别是如果 table 不能被完全缓存。
如果它执行了 "table scan"(如您所见),则代码如下所示:
- 将整个 table 读入 tmp table。 (这将是
MEMORY
或 MyISAM
,具体取决于几个条件。)
- 对 tmp table 进行排序(在磁盘上的 RAM 中)。
table 扫描 + 排序 可能 会更快。
其他说明
如果您的城市在美国,"Los Ranchos de Albuquerque",NM,对于该字段来说太长了。对于世界,请考虑 KZ 中的 87 个字符 "Imeni 50-letiya (Pyat'desyatiletiya) Kazakhskoy Sovetskoy Sotsialisticheskoy Respubliki"。
不使用CHAR
可变长度字符串(城市、地区);使用 VARCHAR
;它将在 space 中更有效率,因此在速度上。
固定长度country_code
使用CHAR(3)
,但一定要指定CHARACTER SET ascii
。如果使用默认的utf8,则需要9个字节,而不是3个。
我在 MySQL 中有这个 table:
Table: city
Columns:
ID int(11) AI PK
Name char(35)
CountryCode char(3)
District char(20)
Population int(11)
我想在按 CountryCode 排序时使用索引,所以我在此列上创建了索引:
Index: CountryCode
Definition:
Type BTREE
Unique No
Columns CountryCode
为什么 EXPLAIN EXTENDED SELECT * FROM city ORDER BY CountryCode; 不使用索引:
| id: 1
| select type: SIMPLE
| table: city
| partitions: NULL
| type: ALL
| possible_keys: NULL
| key: NULL
| key_len: NULL
| ref: NULL
| rows: 4188
| filtered: 100.00
| Extra: Using filesort
and EXPLAIN EXTENDED SELECT id FROM world.city ORDER BY CountryCode; use index:
| id: 1
| select type: SIMPLE
| table: city
| partitions: NULL
| type: index
| possible_keys: NULL
| key: CountryCode
| key_len: 3
| ref: null
| rows: 4188
| filtered: 100.00
| Extra: Using index
如何更改此行为 - 即在第一个示例中添加索引用法?
没问题。第一个查询运行 faster by not using the index.
首先,让我们剖析第二个查询。显然你正在使用 InnoDB。这意味着 INDEX(country_code)
实际上是 INDEX(country_code, id)
。也就是说,PRIMARY KEY
被隐式附加到任何辅助键上。第二个查询只需要country_code
和id
,所以完全可以在索引中执行,如Using index
所示。它按顺序读取索引,并提供结果。效率很高。
现在,让我们看一下第一个查询。这次您要求 *
,而不仅仅是 id
。其余字段不在索引中。如果要使用索引(您可以使用 ... FROM city FORCE INDEX(CountryCode) ...
进行测试),则必须这样做:
- 扫描索引 -- 这会按顺序提供
ids
,但不会提供*
的其余部分。 - 对于每个 id,获取其余
*
的数据。这是昂贵的,特别是如果 table 不能被完全缓存。
如果它执行了 "table scan"(如您所见),则代码如下所示:
- 将整个 table 读入 tmp table。 (这将是
MEMORY
或MyISAM
,具体取决于几个条件。) - 对 tmp table 进行排序(在磁盘上的 RAM 中)。
table 扫描 + 排序 可能 会更快。
其他说明
如果您的城市在美国,"Los Ranchos de Albuquerque",NM,对于该字段来说太长了。对于世界,请考虑 KZ 中的 87 个字符 "Imeni 50-letiya (Pyat'desyatiletiya) Kazakhskoy Sovetskoy Sotsialisticheskoy Respubliki"。
不使用CHAR
可变长度字符串(城市、地区);使用 VARCHAR
;它将在 space 中更有效率,因此在速度上。
固定长度country_code
使用CHAR(3)
,但一定要指定CHARACTER SET ascii
。如果使用默认的utf8,则需要9个字节,而不是3个。