【问题标题】:MySQL indexes performance on huge tablesMySQL 在大表上索引性能
【发布时间】:2015-08-13 05:44:23
【问题描述】:

TL;DR: 我有 2 个大表的查询。它们不是索引。它很慢。因此,我建立索引。它更慢。为什么这有意义?优化它的正确方法是什么?

背景:

我有两张桌子

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

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

这 2 个表很大,除了各自的 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 秒才能运行。这是意料之中的,因为数据很大,需要写入磁盘,而且它们不是表上的索引。

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 个表建立了一堆索引,

/* 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 表中有大约 5'000'000 条记录。
  • 其中只有 130'000 人拥有有效的(不是 NULL)生日
  • 我确实有一个department 表,其中包含大约3'000'000 条记录(它们实际上是项目,而不是部门

【问题讨论】:

    标签: mysql sql database performance indexing


    【解决方案1】:

    对于这个查询:

    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 表中添加最大出生日期,并添加触发器。对于works_in,您需要updateinsertdelete 的触发器。对于persons,只有update(大概insertdelete 将由works_in 处理)。这样就节省了最后的group by,应该是一大笔节省。

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

    【讨论】:

    • 使用您提出的第一个索引(person(birthdate, id)works_in(person_id, department)),查询在 9 秒内运行,大约是初始运行时间的 50%。至少它不再增加运行时间了!由于它们有很多 NULL,因此使用第二组查询/索引在大约 30 秒内运行。感谢您的输入。关于如何改进此结果的任何想法?
    • 你知道有多少部门,有多少记录有有效的生日吗?另外,你有departments 表吗?
    • 是的,我有这个信息。我在最后用这些信息更新了问题(person 中的 10^6 条记录,10^3 条有效记录,我有一个部门表,包含 10^6 条记录)
    【解决方案2】:

    索引提高了 MyISAM 表的性能。它会降低 InnoDB 表的性能。

    在您希望查询最多的列上添加索引。数据关系增长得越复杂,尤其是当这些关系与自身/自身相关时(例如内部连接),每个查询的性能就越差。

    使用索引,引擎必须使用索引来获取匹配值,这很快。然后它必须使用匹配项来查找表中的实际行。如果索引没有缩小行数,那么只查找表中的所有行会更快。

    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

    【讨论】:

    • 感谢您指出 InnoDB/MyISAM 问题,但遗憾的是 InnoDB 是必需的。我更新了我的问题以使其更清楚。
    • 经常是这样。我的观点是索引本身并不是灵丹妙药。
    • InnoDB/MyISAM 问题并非普遍存在。在某些情况下它是倒退的。这与 Gordon 的建议无关。
    • InnoDB 的PRIMARY KEY 确实不需要 需要到达其他地方来获取数据。这使它更快。 InnoDB 中的二级索引可能会更慢,因为它必须向下钻取 2 个 BTree(而 MyISAM 是 1 个)。 InnoDB 中的二级索引有时更快,因为它隐含包含PRIMARY KEY,从而允许在额外情况下“覆盖”。
    • 我发现了这个问题并阅读了“它会降低 innodb 的性能”,因为我正在寻找优化对具有 100,000 行的巨大表的查找。使用 MySQL 5.7 和 innodb 作为引擎,向查找列添加索引将我的每个查询的查找速度提高了 1500%。想把这则轶事留给那些在读完这篇文章后可能会被说服不要这样做的人。
    猜你喜欢
    • 2017-04-09
    • 2020-10-03
    • 1970-01-01
    • 2013-08-08
    • 2011-09-18
    • 1970-01-01
    • 2014-09-04
    • 2022-12-31
    • 2011-06-29
    相关资源
    最近更新 更多