MySQL 中的索引按 DESC、BETWEEN 和几个可能的查询字段集排序

Indexes in MySQL for order by DESC, BETWEEN and several possible sets of fields for query

我目前正在构建一个约会网站,因此主要的性能瓶颈预计来自于获取用户配置文件(并且很少添加新的配置文件 - 所以我们阅读的次数多于写入的次数)。

目前,我有两个 table

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

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

两者都是通过ID连接的(两个table是同一个号码,有约束条件)

(我亲自放置了 user_registration_date, user_last_activity 个字段 table 以便不使用连接)

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

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 条件,并且如果它包含在索引中的顺序与 [ 中的条件顺序相对应=64=]查询).

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

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

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

例如- 使用想要过滤 (sex = 1, orientation =2) 或 (height > 180 and weight < 100) 我需要创建每个可能的指标组合? (听起来很疯狂)

5) 我怎样才能进一步优化我的查询? (我需要使用 order by,limit 和 offset 来分页)

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

1) Is it possible in MYSQL to use several BETWEEN conditions while using indexes for it?

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

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

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

每个子查询可以使用不同的索引。这不是非常有效,但比完整的 table 扫描要好。

(... I have concluded that - MySQL can use only the first BETWEEN condition and if it is included in the index in the order that corresponds to order of conditions in SELECT query)

正确。

2) Does MySQL use indexes for ORDER BY DESC

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

3) Do I need to put the ID filed in the compound index?

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

(... in the best case scenario - will it lead to MYSQL not having to read the real table at all, only reading data from Indexes?)

如果将满足查询所需的所有列放入索引中,查询计划器就不需要读取真正的 table。这就是所谓的复合覆盖指数。

4) How do I create compound indexes for different sets of fields?

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

什么样的色谱柱选择性高?出生日期可能会,因为其中有广泛的值分布。性别通常不会,因为大多数值具有两个值之一。

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

e.g. - users want to filter ( sex = 1, orientation =2) or (height > 180 and weight < 100)

OR是一个特例,因为OR子句的两边都不能用来缩小搜索范围。您可能需要使用上面提到的 WHERE id IN (subquery) 模式。

5) How can I possible further optimize my query? (I need use order by, limit and offset for pagination)

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

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

Where in an index should a column be placed to support 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 要求。

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

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

    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 。这是reader的线索;它对 space 或性能没有影响。

  • "Reading entirely from the index"被称为"index scan"(如果扫描),"Using index"(在EXPLAIN),或"covering index"(理论上讨论)。它可能更快,因为它可能使列的顺序更好,或者索引可能更小。如果 table/index 大于可以在 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,...) 将是最佳选择。

  • 因为你似乎有很多 true/false 标志,考虑使用 SETINT 来容纳一堆。索引无济于事(除非覆盖),但它会显着缩小 table.

  • 的大小
  • 我在这里讨论更多关于创建索引的内容:http://mysql.rjweb.org/doc.php/index_cookbook_mysql

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

但让我们回到真正的问题 -- 您有大量的属性,用户可以指定它们的任何子集。这导致无法优化的情况。因此,我建议将属性的子集(最常用的属性)标识为列。然后把剩下的丢进不是被MySQL看过的JSON字符串。相反,该应用程序会进行第二级过滤。使用 'common' 列,创建一些 2 列或 3 列索引。 (请注意我上面的 a,b,c 示例。)

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

另一个想法:

sex + orientation 可能会变成 ENUM('MF', 'FM', 'MM', 'FF', ...),其中 MF 表示 "Male looking for Female"。并将该列用作大多数索引中的第一列。 (好吧,我不知道如何以实际的方式表示 "Mail looking for Either"。它可能涉及两个查询的 UNION。)