MySQL 中的索引,用于按 DESC、BETWEEN 和几个可能的字段集进行查询

Posted

技术标签:

【中文标题】MySQL 中的索引,用于按 DESC、BETWEEN 和几个可能的字段集进行查询【英文标题】:Indexes in MySQL for order by DESC, BETWEEN and several possible sets of fields for query 【发布时间】:2018-07-26 09:53:42 【问题描述】:

我目前正在构建一个约会网站,因此主要的性能瓶颈预计来自获取用户个人资料(并且很少添加新个人资料 - 因此我们阅读的频率高于写入频率)。

目前,我有两张桌子

1) 用户 - (id, user_name,email,password)

2) 人 - (id, sex, age, sexual_oreintationm, user_registration_date, user_last_activity 等等 - 相当多的字段)

都是通过ID连接的(两张表是同一个编号,有约束)

(我在 person 表中放置了user_registration_date, user_last_activity 字段以便不使用连接)

这是检索数据的一般查询(但字段可能会有所不同)

select * from
(SELECT person.id
FROM person
left join site_users on person.id=site_users.id
where
sex =1
and sexual_orientation =1
and relationship =1
and employment = 1
and smoke = 1
and alcohol =1
and sport = 1
and health = 1
and virus_hiv =1
and virus_hepatitis_c = 1
and (height BETWEEN 110 and 180)
and (weight BETWEEN 50 and 250)
and education > 1
order by site_users.user_registration_date
Limit 50 offset 0) as t
join person on  t.id=person.id
join site_users on t.id = site_users.id;

所有与复合索引相关的问题

1) MYSQL 是否可以在使用索引的同时使用多个 BETWEEN 条件? (在测试中我得出的结论是 - MSYQL 只能使用第一个 BETWEEN 条件,并且如果它按照与 SELECT QUERY 中的条件顺序相对应的顺序包含在索引中)。

2) mysql 是否使用ORDER BY DESC 的索引(例如user_registation_date)?我需要在复合索引中的哪个确切位置放置 user_registation_date 文件才能使其工作?

3) 我需要将ID 归档在复合索引中吗?具体在什么地方? (我的意思是在最好的情况下——它会导致 MYSQL 根本不必读取真实的表,只从索引中读取数据吗?)

4) 如何为不同的字段集创建复合索引?

例如- 用户想要过滤 (sex = 1,orientation =2) 或 (height > 180 and weight ) 我需要创建所有可能的索引组合吗? (听起来很疯狂)

5) 如何进一步优化我的查询? (我需要使用 order by、limit 和 offset 进行分页)

【问题讨论】:

尝试将您的查询输入到 EXPLAIN 语句 (dev.mysql.com/doc/refman/5.7/en/using-explain.html) 中,以准确了解如何/使用哪些索引并识别瓶颈。 MySQL Workbench 中还有一个图形工具。 【参考方案1】:

阅读https://use-the-index-luke.com

1) MYSQL 是否可以在使用索引的同时使用多个 BETWEEN 条件?

简单的答案是否定的,查询规划器只能对多列索引的第一列进行范围扫描。

更复杂的答案是做这样的事情

SELECT id, whatever
FROM tbl
WHERE col1 BETWEEN val AND val
  AND id IN (SELECT id FROM whatever WHERE col2 BETWEEN x AND y)

每个子查询可以使用不同的索引。这效率不高,但比全表扫描要好。

(...我得出的结论是 - MySQL 只能使用第一个 BETWEEN 条件,并且如果它以与 SELECT 查询中的条件顺序相对应的顺序包含在索引中)

正确。

2) MySQL 是否对 ORDER BY DESC 使用索引

是的。在 MySQL 8 中,开发人员添加了descending indexes,这有助于提高ORDER BY ... DESC 的效率。但它在任何情况下都可以使用索引。 (例如用户注册日期)?我需要在复合索引中的哪个确切位置放置 user_registation_date 文件才能使其工作?

3) 我需要将ID 归档在复合索引中吗?

在 InnoDB 表中,pk 是每个索引的隐含部分。所以,在 InnoDB 中,没有。在 MyISAM 中,是的。

(...在最好的情况下——它会导致 MYSQL 根本不必读取真实的表,只从索引中读取数据吗?)

如果将满足查询所需的所有列都放入索引中,则查询计划器不需要读取真实表。这称为复合覆盖索引。

4) 如何为不同的字段集创建复合索引?

如果您有许多搜索条件组合并且必须使用索引来搜索它们,则需要适当组合的索引。这确实可以让你认为你需要大量的索引。但请记住,您可以使用索引来缩小搜索范围,然后逐一扫描更少的行来完成其余的过滤。如果您索引具有高选择性的列,这有助于提高性能,但并不完美。

Wnat 类型的色谱柱选择性高吗?出生日期可能会这样做,因为其中存在广泛的值分布。性别通常不会,因为大多数值都有两个值之一。

您可以随时在发现需要时添加索引。随着数据库在生产中的增长,通常会根据经验添加(和删除)索引。

例如- 用户想要过滤(sex = 1,orientation =2)或(height > 180 and weight

OR 是一种特殊情况,因为OR 子句的两边都不能用来缩小搜索范围。您可能希望对这些使用上述WHERE id IN (subquery) 模式。

5) 如何进一步优化我的查询? (我需要使用 order by、limit 和 offset 进行分页)

SELECT lots of stuff ... ORDER BY ... LIMIT ... OFFSET ... 是一个臭名昭著的性能反模式。为什么?查询计划器对大量数据进行排序,然后丢弃大部分数据。您可以尝试延迟加入。这使用子查询来检索相关的 id,然后加入详细信息。像这样的:

   SELECT whatever, whatever, whatever ...
     FROM table a
    WHERE id IN (
                  SELECT id  
                    FROM table
                   WHERE filter-criterion
                     AND filter-criterion
                   ORDER BY something DESC, anotherthing
                   LIMIT k OFFSET j
                )
    ORDER BY something DESC, anotherthing

这允许查询计划器使用限制和偏移量对更少的列进行排序,然后检索所需行的子集所需的所有列。

应将列放置在索引的哪个位置以支持ORDER BY thatcolumn

一个索引是随机访问的,然后在一个有效的查询中顺序访问。

例如

 SELECT whatever
   FROM table
  WHERE gender='f'
    AND category = 1
    AND dob >= '2001-01-01
    AND dob < '2010-01-01'
  ORDER BY acoount_balance

利用(category, gender, dob, account_balance) 上的 BTREE(排序)索引,因为它可以随机访问索引到第一个符合条件的条目,然后按顺序扫描它到最后一个符合条件的条目。当它扫描每个条目时,它会获取account_balance 值并使用它进行排序。这基本上涵盖了索引行为。

 SELECT whatever
   FROM table
  WHERE gender='f'
    AND category = 1
    AND dob >= '2001-01-01
    AND dob < '2010-01-01'
  ORDER BY dob

是一种特殊情况。在找到第一个 elibile 索引条目后,MySQL 会利用其 ORDER BY 要求可以满足这一事实,因为它会按顺序扫描索引。

专业提示:在构建用于生产用途的新应用程序时,不要过度考虑这些索引内容。在您的表变大之前,您不需要复杂的索引。当它们确实变大时,您会发现您对正确索引的猜测至少有些错误。在一个不断增长的现实世界数据库中,标准做法是每隔几周查看一次慢查询,使用EXPLAIN 找出 MySQL 如何满足它们,并根据需要添加或删除索引以提高用户实际情况的性能关心。

【讨论】:

任何子查询可以使用不同的索引吗?就像在这个例子中 SELECT id,whatever FROM tbl WHERE col1 BETWEEN val AND val AND id IN (SELECT id From whatever WHERE col2 BETWEEN x AND y) 我只需要创建一个索引,其中包含唯一的一个字段 - col2 和 sub子查询可以单独使用吗?哪个更好 - “id IN”或“Union unique”?我在哪里(什么位置)在复合索引中放置“order by”字段。?最后一个?如果索引没有被完全使用(没有到最后一个位置),它会起作用【参考方案2】:

(不同意 O. Jones。)我严重怀疑这个示例的外部部分是否能够有效地使用两个索引并且速度更快。一旦到达外部,它将需要col1id 的复合索引,但它不会超过第一列,因为它是一个“范围”。

WHERE col1 BETWEEN val AND val AND id IN (SELECT id FROM whatever WHERE col2 BETWEEN x AND y)

即使在 MySQL 8 之前,索引也可以用于 ORDER BY x DESC -- 但是您的复杂查询不太可能通过 WHERE 到达 ORDER BY,无论是 ASC 还是 DESC,无论是8.0 或更早版本。

我建议将id 添加到任何索引的末尾您希望使用它的位置。这是给读者的线索;它对空间或性能没有影响。

“完全从索引读取”称为“索引扫描”(如果扫描)、“使用索引”(在EXPLAIN)或“覆盖索引”(在理论讨论中)。它可能会更快,因为它可能具有更好的列顺序或索引可能更小。如果表/索引大于可以缓存在 RAM 中的大小,后一种情况特别方便。

使用由=(性、运动、...)测试的字段开始一个复合索引,然后您有一个机会为范围。

如果WHERE 全部为=,并且您以恰好那组列开始索引(在您的应用中可能不实用),那么 添加ORDER BY 列。然后,优化器可能避免对ORDER BY进行排序,并且可能能够以LIMIT停止。

使用WHERE a=1 AND c=2(未提及b),INDEX(a,b,c) 将无法通过a。相反,INDEX(a,c,...)INDEX(c,a,...) 将是最佳选择。

由于您似乎有很多真/假标志,请考虑使用SETINT 来保存一堆。索引无济于事(除非覆盖),但它会显着缩小表的大小。

我在这里讨论有关制作索引的更多信息:http://mysql.rjweb.org/doc.php/index_cookbook_mysql

只有 MySQL 8.0 可以处理优化方向混合:ORDER BY x ASC, y DESC。 (旧版本通过收集可能的行,排序,然后查看LIMITOFFSET来处理它。)

但是让我们回到真正的问题——你有大量的属性,用户可以指定它们的任何子集。这会导致无法优化的情况。因此,我建议将属性子集(最常用的属性)标识为列。然后将其余部分放入 MySQL 未查看的JSON 字符串中。相反,该应用程序执行第二级过滤。使用“通用”列,创建一些 2 或 3 列索引。 (请注意我上面的 a,b,c 示例。)

EAV 的讨论:http://mysql.rjweb.org/doc.php/eav

另一个想法:

sex + orientation 可能会变成ENUM('MF', 'FM', 'MM', 'FF', ...),其中MF 的意思是“男性寻找女性”。并将此列用作大多数索引中的第一列。 (好吧,我不知道如何用实际的方式表示“Mail looking for Either”。它可能涉及两个查询的UNION。)

【讨论】:

以上是关于MySQL 中的索引,用于按 DESC、BETWEEN 和几个可能的字段集进行查询的主要内容,如果未能解决你的问题,请参考以下文章

是否可以让 MySQL 使用 1 DESC、2 ASC 的 ORDER 索引?

mysql 不同表的字段如何创建索引

DESC 和 ASC 作为存储过程中的参数

如果 ASC 和 DESC 混合使用,为啥 MySQL 不能为 ORDER BY 使用索引?

MySQL

Mysql原理篇之索引不懂不要瞎用---04