为 where 子句和 order_by 创建 MYSQL 索引

Posted

技术标签:

【中文标题】为 where 子句和 order_by 创建 MYSQL 索引【英文标题】:Create a MYSQL index for where clause and order_by 【发布时间】:2021-02-07 09:23:47 【问题描述】:

考虑到这张表,

CREATE TABLE tbl_tax (
  taxdata_id int(11) NOT NULL AUTO_INCREMENT,
  tax_year varchar(255) NOT NULL,
  display_pid varchar(255) NOT NULL,
  type varchar(255) NOT NULL,
  tax_id varchar(255) NOT NULL,
  tax_amount varchar(255) NOT NULL,
  total_due varchar(255) NOT NULL,
  paid_wcert varchar(255) NOT NULL,
  datelast_adv varchar(255) NOT NULL,
  pmtmade_today varchar(255) NOT NULL,
  owner_name varchar(255) NOT NULL,
  PRIMARY KEY (taxdata_id),
  UNIQUE KEY unique_tbl_tax_TaxidYear (tax_id,tax_year),
  KEY tax_year_2 (tax_year, owner_name, tax_id, display_pid, 
    type, tax_amount, total_due, total_paid, datelast_adv, pmtmade_today, 
    taxdata_id, paid_wcert)
) ENGINE=InnoDB AUTO_INCREMENT=100000 DEFAULT CHARSET=latin1;
 tbl_tax;

考虑到这个 SQL 查询,

SELECT tax_year
     , tax_id
     , owner_name
     , display_pid
     , type
     , tax_amount
     , total_due
     , total_paid
     , datelast_adv
     , pmtmade_today
     , taxdata_id
     , paid_wcert
  FROM tbl_tax
 WHERE tax_year >= '2015'
   AND tax_year <= '2019'
 ORDER 
    BY tax_year DESC;

我想创建一个索引并尝试创建一个封面索引。

引自this文章, “一般规则是先选择列进行过滤(具有相等条件的 WHERE 子句),然后排序/分组(ORDER BY 和 GROUP BY 子句),最后是数据投影(SELECT 子句)。”

ALTER TABLE tbl_tax
ADD INDEX (
    `tax_year`, `owner_name`, `tax_id`, `display_pid`, 
    `type`, `tax_amount`, `total_due`, `total_paid`, `datelast_adv`, `pmtmade_today`, 
    `taxdata_id`, `paid_wcert`
);

做一个explain,表演,

        "id" : 1,
        "select_type" : "SIMPLE",
        "table" : "tbl_tax",
        "partitions" : null,
        "type" : "index",
        "possible_keys" : "tax_year_2",
        "key" : "tax_year_2",
        "key_len" : "2831",
        "ref" : null,
        "rows" : 271630,
        "filtered" : 50.00,
        "Extra" : "Using where; Backward index scan; Using index"   

在创建索引时,我知道:-

    包含范围谓词(=)的 WHERE 子句 查询包含 ORDER_BY 的顺序与访问行的顺序不同。

这可能是explain 的输出显示"rows" : 271630, 的原因

但是,SQL 查询的结果集只有 ~2000 行。

尝试阅读许多文章,但我仍在努力优化。

对于这种情况,我可以做些什么来获得更好的优化? 我可以以更好的方式创建索引吗? 我可以对 SQL 查询进行任何更改吗? 另外,如果我在这里误解了什么,请随时纠正我。

【问题讨论】:

我想你可以告诉我们 tax_year_2 请同时显示表格定义。 用更多信息更新了问题。 【参考方案1】:

这是一个有趣的案例,因为通常我们希望在 EXPLAIN 计划中看到Using index,但在这种情况下,这是一个不利因素。

原因是这是type: index,这意味着它正在进行索引扫描。这意味着它正在扫描 整个索引, 而不仅仅是与您的条件匹配的行。这就是为什么它显示rows: 271630。这基本上是您的表的大小(或者至少是优化器根据其统计信息估计的表的大小)。

在这种情况下,我认为将每一列都添加到索引中并没有帮助。使用 one 列的索引会更好:tax_year

那么由于您的条件,我希望 EXPLAIN 显示 type: range,这表明它正在检查的唯一行是符合条件的行。

然后我们会看到Filtered: 100.00,它表示所有检查的行都包含在结果中,这很好。这意味着查询是有效的,因为没有检查任何行但随后被过滤掉了。

此外,由于您的 ORDER BY 是针对同一列的,我仍然希望 Using filesort 不存在,这很好。


你的评论:

我想您在 2015 年和 2019 年之间的 tax_year 条件与表格的很大一部分相匹配。如果您的条件与大部分行匹配,mysql 会选择不使用索引。它估计使用索引比只扫描表的成本更高。

如果您认为优化器做出了错误的选择,您可以提示它应该假定表扫描的成本更高:

... FROM tbl_tax FORCE INDEX(tax_year) ...

(我假设索引的名称是tax_year,但在您的情况下,您应该将其替换为索引的名称。)

我也同意其他人的观点,即您对每个属性列都使用 varchar(255) 是不合适的。

【讨论】:

欣赏这个详细的解释。这真是一个很好的信息。因此,我删除了之前的封面索引,并使用tax_year 列创建了一个新索引。但是,在执行explain 时,得到以下结果:- "type" : "ALL","possible_keys" : "tax_year","key" : null,"rows" : 271630,"filtered" : 50.00,"Extra" : "Using where; Using filesort" 这说明优化器选择不使用您在 tax_year 上创建的索引。相反,它的 table 扫描了 table,然后只过滤掉了匹配的行。这可能是由于基数低,但我肯定会继续努力为您正在存储和想要搜索的数据使用适当的 mysql 数据类型。按照之前的建议将 tax_year 更改为 YEAR 类型将是一个很好的探索途径。 FORCE INDEX() 成功了。持续时间显着改善。【参考方案2】:

INDEX(tax_year, ...) 确实处理WHEREORDER BY

查询包含 ORDER_BY 的顺序与访问行的顺序不同。

错误。 WHERE 没有指定访问它们的顺序。事实上,EXPLAIN 表示“向后索引扫描”。一切都很好。

使用合理的数据类型,例如 tax_year 使用 2 字节的 YEAR 而不是 VARCHAR(255),这需要 6 字节一年。

varchars 上的算术运算(对于“金额”等)会很混乱。

当然,“覆盖”索引会有所帮助。但我不喜欢让索引大于 5 列。你的大索引有助于查询一些,但也伤害了INSERTs

(我同意比尔。)

【讨论】:

不错。我肯定也会负责进行这些更改。

以上是关于为 where 子句和 order_by 创建 MYSQL 索引的主要内容,如果未能解决你的问题,请参考以下文章

EF 6 - 注入where子句

如何在sql中创建动态where子句?

iOS - Firestore 复合索引中的索引上的多个 orderBy 和 where 子句

MAX 在 WHERE 子句中返回所有记录

您如何编写一条SQL语句来为日期值创建新列,然后在WHERE子句中查询它

在 where 子句 SQL 中的 case 语句中使用参数