MySQL 在大表上索引性能
MySQL indexes performance on huge tables
TL;DR:
我有一个关于 2 个巨大 table 的查询。它们不是索引。它很慢。因此,我建立索引。它比较慢。为什么这是有道理的?正确的优化方法是什么?
背景:
我有 2 table
person
,一个 table 包含关于人 (id, birthdate
) 的信息
works_in
,person
与部门之间的0-N关系; works_in
包含 id, person_id, department_id
.
它们是 InnoDB tables,遗憾的是不能切换到 MyISAM,因为数据完整性是一项要求。
这 2 个 table 很大,除了它们各自的 id
.
上的 PRIMARY
之外不包含任何索引
我正在尝试获取每个部门中最年轻的人的年龄,这是我提出的查询
SELECT MAX(YEAR(person.birthdate)) as max_year, works_in.department as department
FROM person
INNER JOIN works_in
ON works_in.person_id = person.id
WHERE person.birthdate IS NOT NULL
GROUP BY works_in.department
查询有效,但我对性能不满意,因为 运行 需要 ~17 秒。这是预料之中的,因为数据很大,需要写入磁盘,而且它们在 tables 上没有索引。
EXPLAIN
对于此查询给出
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|----|-------------|---------|--------|---------------|---------|---------|--------------------------|----------|---------------------------------|
| 1 | SIMPLE | works_in| ALL | NULL | NULL | NULL | NULL | 22496409 | Using temporary; Using filesort |
| 1 | SIMPLE | person | eq_ref | PRIMARY | PRIMARY | 4 | dbtest.works_in.person_id| 1 | Using where |
我为 2 table 建立了一堆索引,
/* For works_in */
CREATE INDEX person_id ON works_in(person_id);
CREATE INDEX department_id ON works_in(department_id);
CREATE INDEX department_id_person ON works_in(department_id, person_id);
CREATE INDEX person_department_id ON works_in(person_id, department_id);
/* For person */
CREATE INDEX birthdate ON person(birthdate);
EXPLAIN
显示了改进,至少我是这样理解的,看到它现在使用索引并扫描更少的行。
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|----|-------------|---------|-------|--------------------------------------------------|----------------------|---------|------------------|--------|-------------------------------------------------------|
| 1 | SIMPLE | person | range | PRIMARY,birthdate | birthdate | 4 | NULL | 267818 | Using where; Using index; Using temporary; Using f... |
| 1 | SIMPLE | works_in| ref | person,department_id_person,person_department_id | person_department_id | 4 | dbtest.person.id | 3 | Using index |
但是,查询的执行时间增加了一倍(从 ~17s 到 ~35s)。
为什么这是有道理的,优化它的正确方法是什么?
编辑
使用 Gordon Linoff 的答案(第一个),执行时间约为 9 秒(初始值的一半)。选择好的索引似乎确实有帮助,但执行时间仍然很长。关于如何对此进行改进还有其他想法吗?
有关数据集的更多信息:
person
table中有大约 5'000'000 条记录。
- 其中只有 130'000 人有有效的(不是
NULL
)生日
- 我确实有一个
department
table,其中包含大约 3'000'000 条记录(它们实际上是 projects 而不是 部门)
索引提高了 MyISAM 表的性能。它会降低 InnoDB 表的性能。
在您希望查询最多的列上添加索引。数据关系变得越复杂,尤其是当这些关系与自身相关时(例如内连接),每个查询的性能就越差。
With an index, the engine has to use the index to get matching values, which is fast. Then it has to use the matches to look up the actual rows in the table. If the index doesn't narrow down the number of rows, it can be faster to just look up all the rows in the table.
When to add an index on a SQL table field (MySQL)?
When to use MyISAM and InnoDB?
https://dba.stackexchange.com/questions/1/what-are-the-main-differences-between-innodb-and-myisam
对于此查询:
SELECT MAX(YEAR(p.birthdate)) as max_year, wi.department as department
FROM person p INNER JOIN
works_in wi
ON wi.person_id = p.id
WHERE p.birthdate IS NOT NULL
GROUP BY wi.department;
最好的索引是:person(birthdate, id)
和 works_in(person_id, department)
。这些是查询的覆盖索引,省去了读取数据页的额外开销。
顺便说一下,除非很多人的生日都是NULL
(即有些部门每个人的生日都是NULL
),查询基本等同于:
SELECT MAX(YEAR(p.birthdate)) as max_year, wi.department as department
FROM person p INNER JOIN
works_in wi
ON wi.person_id = p.id
GROUP BY wi.department;
为此,最好的索引是 person(id, birthdate)
和 works_in(person_id, department)
。
编辑:
我想不出解决问题的简单方法。一种解决方案是更强大的硬件。
如果您真的很快就需要这些信息,则需要额外的工作。
一种方法是向 departments
table 添加最大出生日期并添加触发器。对于 works_in
,您需要 update
、insert
和 delete
的触发器。对于 persons
,只有 update
(大概 insert
和 delete
将由 works_in
处理)。这样就节省了最后的group by
,应该是一个很大的节省。
一种更简单的方法是将最大出生日期添加到 works_in
。但是,您仍然需要最终聚合,这可能会很昂贵。
TL;DR: 我有一个关于 2 个巨大 table 的查询。它们不是索引。它很慢。因此,我建立索引。它比较慢。为什么这是有道理的?正确的优化方法是什么?
背景:
我有 2 table
person
,一个 table 包含关于人 (id, birthdate
) 的信息
works_in
,person
与部门之间的0-N关系;works_in
包含id, person_id, department_id
.
它们是 InnoDB tables,遗憾的是不能切换到 MyISAM,因为数据完整性是一项要求。
这 2 个 table 很大,除了它们各自的 id
.
PRIMARY
之外不包含任何索引
我正在尝试获取每个部门中最年轻的人的年龄,这是我提出的查询
SELECT MAX(YEAR(person.birthdate)) as max_year, works_in.department as department
FROM person
INNER JOIN works_in
ON works_in.person_id = person.id
WHERE person.birthdate IS NOT NULL
GROUP BY works_in.department
查询有效,但我对性能不满意,因为 运行 需要 ~17 秒。这是预料之中的,因为数据很大,需要写入磁盘,而且它们在 tables 上没有索引。
EXPLAIN
对于此查询给出
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|----|-------------|---------|--------|---------------|---------|---------|--------------------------|----------|---------------------------------|
| 1 | SIMPLE | works_in| ALL | NULL | NULL | NULL | NULL | 22496409 | Using temporary; Using filesort |
| 1 | SIMPLE | person | eq_ref | PRIMARY | PRIMARY | 4 | dbtest.works_in.person_id| 1 | Using where |
我为 2 table 建立了一堆索引,
/* For works_in */
CREATE INDEX person_id ON works_in(person_id);
CREATE INDEX department_id ON works_in(department_id);
CREATE INDEX department_id_person ON works_in(department_id, person_id);
CREATE INDEX person_department_id ON works_in(person_id, department_id);
/* For person */
CREATE INDEX birthdate ON person(birthdate);
EXPLAIN
显示了改进,至少我是这样理解的,看到它现在使用索引并扫描更少的行。
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|----|-------------|---------|-------|--------------------------------------------------|----------------------|---------|------------------|--------|-------------------------------------------------------|
| 1 | SIMPLE | person | range | PRIMARY,birthdate | birthdate | 4 | NULL | 267818 | Using where; Using index; Using temporary; Using f... |
| 1 | SIMPLE | works_in| ref | person,department_id_person,person_department_id | person_department_id | 4 | dbtest.person.id | 3 | Using index |
但是,查询的执行时间增加了一倍(从 ~17s 到 ~35s)。
为什么这是有道理的,优化它的正确方法是什么?
编辑
使用 Gordon Linoff 的答案(第一个),执行时间约为 9 秒(初始值的一半)。选择好的索引似乎确实有帮助,但执行时间仍然很长。关于如何对此进行改进还有其他想法吗?
有关数据集的更多信息:
person
table中有大约 5'000'000 条记录。- 其中只有 130'000 人有有效的(不是
NULL
)生日 - 我确实有一个
department
table,其中包含大约 3'000'000 条记录(它们实际上是 projects 而不是 部门)
索引提高了 MyISAM 表的性能。它会降低 InnoDB 表的性能。
在您希望查询最多的列上添加索引。数据关系变得越复杂,尤其是当这些关系与自身相关时(例如内连接),每个查询的性能就越差。
With an index, the engine has to use the index to get matching values, which is fast. Then it has to use the matches to look up the actual rows in the table. If the index doesn't narrow down the number of rows, it can be faster to just look up all the rows in the table.
When to add an index on a SQL table field (MySQL)?
When to use MyISAM and InnoDB?
https://dba.stackexchange.com/questions/1/what-are-the-main-differences-between-innodb-and-myisam
对于此查询:
SELECT MAX(YEAR(p.birthdate)) as max_year, wi.department as department
FROM person p INNER JOIN
works_in wi
ON wi.person_id = p.id
WHERE p.birthdate IS NOT NULL
GROUP BY wi.department;
最好的索引是:person(birthdate, id)
和 works_in(person_id, department)
。这些是查询的覆盖索引,省去了读取数据页的额外开销。
顺便说一下,除非很多人的生日都是NULL
(即有些部门每个人的生日都是NULL
),查询基本等同于:
SELECT MAX(YEAR(p.birthdate)) as max_year, wi.department as department
FROM person p INNER JOIN
works_in wi
ON wi.person_id = p.id
GROUP BY wi.department;
为此,最好的索引是 person(id, birthdate)
和 works_in(person_id, department)
。
编辑:
我想不出解决问题的简单方法。一种解决方案是更强大的硬件。
如果您真的很快就需要这些信息,则需要额外的工作。
一种方法是向 departments
table 添加最大出生日期并添加触发器。对于 works_in
,您需要 update
、insert
和 delete
的触发器。对于 persons
,只有 update
(大概 insert
和 delete
将由 works_in
处理)。这样就节省了最后的group by
,应该是一个很大的节省。
一种更简单的方法是将最大出生日期添加到 works_in
。但是,您仍然需要最终聚合,这可能会很昂贵。