【问题标题】:MySQL `FORCE INDEX` use cases?MySQL `FORCE INDEX` 用例?
【发布时间】:2011-12-07 12:57:54
【问题描述】:

几乎在我读到的所有地方都非常不鼓励使用FORCE INDEX,我完全理解并知道为什么——MySQL 比(普通)开发人员更清楚要选择哪些索引的可能性非常大。

然而,最近我发现了一个案例,FORCE INDEX 将我的执行时间提高了数百倍:

  • JOIN 4 张桌子
  • 第一个表有大约 500 000 条记录
  • INNER JOINed 表中有 2 条记录超过 100 万条
  • 第一个表有一个名为published_date的字段,以YMD格式存储为varchar(无法更改为datetime
  • 需要published_date 的范围内最多 5 000 条记录
  • 此查询需要第一个表上的其他一些 GROUP BYORDER BY 子句与 published_date 不同的字段

虽然我以多种方式重写了查询,但我无法获得小于 130 秒的执行时间(最高超过 700 秒)。将FORCE INDEXpublished_date 一起使用后,执行时间降至5 秒以下。

我花了几天时间才想起臭名昭著的 FORCE INDEX 选项。

问题:

  • 您发现FORCE INDEX 拯救了您的其他用例还有哪些?
  • 您在考虑使用FORCE INDEX 时是否有一些最佳做法

编辑 - 观察: 我在这里也用这个问题创建了this blog post。您提供的所有答案也会出现在那里 - 包括学分和您想要的所有东西。

编辑 2

我应用了我在您的 cmets 中收到的建议(ANALYZE TABLEOPTIMIZE TABLE),下面是在查询中应用的 EXPLAIN 的输出 - 不幸的是,索引选择并没有更好:

1. 没有FORCE INDEX 别名a

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  am2 range   PRIMARY,idx_meta_article    idx_meta_article    4   NULL    275228  Using where; Using index; Using temporary; Using f...
1   SIMPLE  a   eq_ref  PRIMARY,serial_issue_date_productid,pub_date,idx_d...   PRIMARY 4   mydb_toto.am2.ArticleID 1   Using where
1   SIMPLE  ai  ref PRIMARY,idx_iso_article PRIMARY 4   mydb_toto.a.serial  11523   Using where; Using index
1   SIMPLE  m   range   PRIMARY,meta_articles_type  meta_articles_type  4   NULL    96  Using where
1   SIMPLE  am  eq_ref  PRIMARY,idx_meta_article    PRIMARY 8   mydb_toto.a.serial,mydb_toto.m.meta_id  1   Using where; Using index

2. FORCE INDEX 在桌子上,别名 a:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  a   range   pub_date    pub_date    11  NULL    17679   Using where; Using temporary; Using filesort
1   SIMPLE  am2 ref PRIMARY,idx_meta_article    PRIMARY 4   mydb_toto.a.serial  21930   Using where; Using index
1   SIMPLE  ai  ref PRIMARY,idx_iso_article PRIMARY 4   mydb_toto.a.serial  11523   Using where; Using index
1   SIMPLE  m   range   PRIMARY,meta_articles_type  meta_articles_type  4   NULL    96  Using where
1   SIMPLE  am  eq_ref  PRIMARY,idx_meta_article    PRIMARY 8   mydb_toto.am2.ArticleID,mydb_toto.m.meta_id 1   Using where; Using index

3. 在ANALYZE TABLE之后,没有FORCE INDEX

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  am2 range   PRIMARY,idx_meta_article    idx_meta_article    4   NULL    275228  Using where; Using index; Using temporary; Using f...
1   SIMPLE  a   eq_ref  PRIMARY,serial_issue_date_productid,pub_date,idx_d...   PRIMARY 4   mydb_toto.am2.ArticleID 1   Using where
1   SIMPLE  ai  ref PRIMARY,idx_iso_article PRIMARY 4   mydb_toto.a.serial  11523   Using where; Using index
1   SIMPLE  m   range   PRIMARY,meta_articles_type  meta_articles_type  4   NULL    96  Using where
1   SIMPLE  am  eq_ref  PRIMARY,idx_meta_article    PRIMARY 8   mydb_toto.a.serial,mydb_toto.m.meta_id  1   Using where; Using index

4. 在OPTIMIZE TABLE之后,没有FORCE INDEX

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  am2 range   PRIMARY,idx_meta_article    idx_meta_article    4   NULL    275228  Using where; Using index; Using temporary; Using f...
1   SIMPLE  a   eq_ref  PRIMARY,serial_issue_date_productid,pub_date,idx_d...   PRIMARY 4   mydb_toto.am2.ArticleID 1   Using where
1   SIMPLE  ai  ref PRIMARY,idx_iso_article PRIMARY 4   mydb_toto.a.serial  11523   Using where; Using index
1   SIMPLE  m   range   PRIMARY,meta_articles_type  meta_articles_type  4   NULL    96  Using where
1   SIMPLE  am  eq_ref  PRIMARY,idx_meta_article    PRIMARY 8   mydb_toto.a.serial,mydb_toto.m.meta_id  1   Using where; Using index

5. 在OPTIMIZE TABLEANALYZE TABLE 之后,加上FORCE INDEX

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  a   range   pub_date    pub_date    11  NULL    17679   Using where; Using temporary; Using filesort
1   SIMPLE  am2 ref PRIMARY,idx_meta_article    PRIMARY 4   mydb_toto.a.serial  21930   Using where; Using index
1   SIMPLE  ai  ref PRIMARY,idx_iso_article PRIMARY 4   mydb_toto.a.serial  11523   Using where; Using index
1   SIMPLE  m   range   PRIMARY,meta_articles_type  meta_articles_type  4   NULL    96  Using where
1   SIMPLE  am  eq_ref  PRIMARY,idx_meta_article    PRIMARY 8   mydb_toto.am2.ArticleID,mydb_toto.m.meta_id 1   Using where; Using index

【问题讨论】:

  • 您是否在必须FORCE INDEX 的表上运行了ANALYZE TABLE
  • 我的经验还告诉我,您需要指示规划器使用某个索引的情况非常少见,大多数情况下它选择不佳是因为索引损坏或错误,这将通过 ANALYZE 修复.
  • @Romain - 没有分析表运行...好主意
  • @TudorConstantin 您可以尝试分析它们,然后将新的“香草”查询计划与“强制”查询计划进行比较......也许在此之后它们会相同。
  • 强制使用特定索引的问题在于,即使今天取得了更好的性能,也无法轻易预测修改表统计信息的后果,尤其是对于复杂的查询。查询优化器几乎总是选择最佳执行计划,并且还可以适应变化。除了使用 ANALYZE 来更新表统计信息之外,如果您正在进行大量修改(大量的 DELETE/INSERT)也可以对索引页进行排序,您还可以使用 OPTIMIZE dev.mysql.com/doc/refman/5.1/en/optimize-table.html

标签: mysql


【解决方案1】:

我通过您的EXPLAIN 计划注意到表格顺序发生了变化,前两个表格颠倒了,这很可能是您的性能改进的来源,除了使用日期索引。

您是否研究过在查询中使用STRAIGHT_JOIN 来强制表的顺序?

我曾研究过一个大型数据库架构,其中优化的连接配置在整个查询过程中一直使用 STRAIGHT_JOINs,性能比 INNER JOIN 等效项提高了 100 倍。

不幸的是,我无法再访问系统来获取一些示例EXPLAIN 计划,但最佳的表顺序是这样的;

Table 1           10 rows              1 analysed
Table 2           500 rows             50 analysed
Table 3           1,000,000 rows       300,000 analysed
Table 4           500,000,000 rows     4,000,000 analysed

使用STRAIGHT_JOINs 保持此顺序导致查询性能远高于INNER JOIN 等效项,后者实质上只是颠倒了表的顺序。

返回到您的原始查询,删除强制索引,然后将 INNER JOINs 替换为 STRAIGHT_JOINs,然后查看说明计划为您提供了什么。

您可能还想使用pub_dateseriala 表上创建一个复合索引,我认为这将进一步改进查询。

【讨论】:

    【解决方案2】:

    我注意到,当您对 VARCHAR 字段有多个连接和子查询时,FK 和引用的值都不是主键,同时在 DATE 字段上有 where 子句时,FORCE INDEX 会有所帮助。

    类似:

    SELECT NAME, a.reference_no, i.value, p.value FROM customers AS c
    INNER JOIN accounts AS a ON c.id = a.customer_id
    INNER JOIN invoices AS i ON i.reference_no = a.reference_no
    INNER JOIN payments AS p ON p.invoice_no = i.invoice_no
    WHERE payments.date >= '2011-09-01' AND DATE < '2011-10-01';
    

    mysql 将始终使用 PKs 和 FKs,您会首先使用支付表上的 payment_date 索引,因为它是最大的索引。因此,在支付表连接上添加 FORCE INDEX(payment_date) 会有很大帮助。

    这是我们在工作中使用的第三方计费数据库的示例。我们在优化方面遇到了很大的问题,而 FORCE INDEX 大部分时间都完成了这项工作。通常我们用 mysqladmin 发现慢查询,用 FORCE INDEX 测试它们,然后将它们发送给供应商以在应用程序的源代码中重写它们。

    这里有四个表格可以更好地理解这个例子:

    CREATE TABLE `customers` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
     `name` varchar(100) NOT NULL,
     PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
    
    CREATE TABLE `accounts` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `customer_id` int(11) NOT NULL,
      `reference_no` varchar(10) NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `reference_no_uniq` (`reference_no`),
      KEY `FK_accounts` (`customer_id`),
      CONSTRAINT `FK_accounts` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;
    
    CREATE TABLE `invoices` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `reference_no` varchar(10) NOT NULL,
      `invoice_no` varchar(10) NOT NULL,
      `value` int(11) NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `invoice_no_uniq` (`invoice_no`),
      KEY `FK_invoices` (`reference_no`),
      CONSTRAINT `FK_invoices` FOREIGN KEY (`reference_no`) REFERENCES `accounts` (`reference_no`)
    ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1;
    
    CREATE TABLE `payments` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `invoice_no` varchar(10) NOT NULL,
      `value` int(11) NOT NULL,
      `date` datetime DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `FK_payments` (`invoice_no`),
      KEY `payment_date` (`date`),
      CONSTRAINT `FK_payments` FOREIGN KEY (`invoice_no`) REFERENCES `invoices` (`invoice_no`)
    ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
    

    【讨论】:

    • 为什么 MySQL 会一直使用表 customers 的主键?是否取决于连接中表的顺序?
    • 我已经编辑了帖子...我遇到的更大问题是没有在付款表上使用日期索引。但是我认为表的顺序确实会影响正在使用的索引的顺序
    • 你说得对,但不要在任何地方向我们展示这个 FORCED_INDEX 解决方案的任何参考。如果您建议我们在第一个查询中使用它,为什么不列出第二个实现 FORCED_INDEX 的查询呢?感谢您提供帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多