为 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, ...)
确实处理WHERE
和ORDER BY
。
查询包含 ORDER_BY 的顺序与访问行的顺序不同。
错误。 WHERE
没有指定访问它们的顺序。事实上,EXPLAIN
表示“向后索引扫描”。一切都很好。
使用合理的数据类型,例如 tax_year
使用 2 字节的 YEAR
而不是 VARCHAR(255)
,这需要 6 字节一年。
varchars 上的算术运算(对于“金额”等)会很混乱。
当然,“覆盖”索引会有所帮助。但我不喜欢让索引大于 5 列。你的大索引有助于查询一些,但也伤害了INSERTs
。
(我同意比尔。)
【讨论】:
不错。我肯定也会负责进行这些更改。以上是关于为 where 子句和 order_by 创建 MYSQL 索引的主要内容,如果未能解决你的问题,请参考以下文章
iOS - Firestore 复合索引中的索引上的多个 orderBy 和 where 子句