MySQL 在大表上索引性能

Win*_*nks 6 mysql sql database indexing performance

长话短说: 我对 2 个巨大的表有一个查询。它们不是索引。它很慢。因此,我建立索引。它比较慢。为什么这是有道理的?正确的优化方法是什么?

的背景:

我有2张桌子

  • person,包含人员信息的表 ( id, birthdate)
  • works_inperson,与部门之间的0-N关系;works_in包含id, person_id, department_id.

它们是 InnoDB 表,遗憾的是不能选择切换到 MyISAM,因为数据完整性是一项要求。

这两个表很大,除了各自PRIMARYid.

我正在尝试获取每个部门中最年轻的人的年龄,这是我提出的查询

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
Run Code Online (Sandbox Code Playgroud)

该查询有效,但我对性能不满意,因为运行需要大约 17 秒。这是预料之中的,因为数据很大并且需要写入磁盘,并且它们不是表上的索引。

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                     | 
Run Code Online (Sandbox Code Playgroud)

我为这两个表建立了一堆索引,

/* 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);
Run Code Online (Sandbox Code Playgroud)

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                                           | 
Run Code Online (Sandbox Code Playgroud)

然而,查询的执行时间增加了一倍(从约 17 秒到约 35 秒)。

为什么这是有道理的,优化它的正确方法是什么?

编辑

使用 Gordon Linoff 的答案(第一个),执行时间约为 9 秒(初始时间的一半)。选择好的索引似乎确实有帮助,但执行时间仍然相当长。关于如何改进这一点还有其他想法吗?

有关数据集的更多信息:

  • 表中大约有 5'000'000 条记录person
  • 其中只有 130'000 人有有效(无效NULL)出生日期
  • 我确实有一个department表,其中包含大约 3'000'000 条记录(它们实际上是项目而不是部门

Gor*_*off 4

对于此查询:

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;
Run Code Online (Sandbox Code Playgroud)

最好的索引是: 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;
Run Code Online (Sandbox Code Playgroud)

为此,最好的索引是person(id, birthdate)works_in(person_id, department)

编辑:

我想不出一个简单的方法来解决这个问题。一种解决方案是更强大的硬件。

如果您确实快速需要此信息,则需要进行额外的工作。

一种方法是向表中添加最大出生日期departments,并添加触发器。对于works_in,您需要updateinsert和 的触发器delete。仅对于persons, update(大概insertdelete将由 处理works_in)。这样就节省了最后的group by,这应该是一笔很大的节省。

一种更简单的方法是将最大出生日期添加到works_in。但是,您仍然需要最终聚合,这可能会很昂贵。