【问题标题】:MySQL index + math operatorsMySQL 索引 + 数学运算符
【发布时间】:2019-07-10 22:48:59
【问题描述】:

我有 RatingTable:

UserID int,
Rating int,
BanMask int,
index rating_index (Rating DESC),
index ban_index (BanMask ASC)

假设此表中有超过 500 万行,并且只有大约 100 个真正被禁止的用户。

如果我对索引字段使用位数学运算,选择查询是否仍会被优化? 这 2 个查询会使用索引优化吗?

SELECT * FROM ProfileTable 
WHERE BanMask > 0 
ORDER BY Rating DESC LIMIT 10;

对

SELECT * FromProfileTable 
WHERE (BanMask & (1 << 2)) > 0 
ORDER BY Rating DESC LIMIT 10;

第二个问题。我应该在 Rating + BanMask 字段上添加索引以获得更好的优化吗?像这样:

CREATE INDEX rating_ban_index ON ProfileTable (Rating DESC, BanMask ASC)

【问题讨论】:

  • MySQL 可能不选择使用任何当前索引的潜在更大原因是:您正在使用SELECT *。您实际需要哪些列?
  • 我真的需要 *,此配置文件表包含完整的配置文件描述:id、名称、游戏统计信息。在这种情况下,选定的列是否重要?我可以在第一遍只选择ID,然后选择数据的重置。
  • 在 BanMask 字段中使用了多少位?该列中的最大值是多少?

标签: mysql optimization indexing


【解决方案1】:

您可以使用EXPLAIN 自行确认给定查询使用了哪些索引。

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: RatingTable
         type: index
possible_keys: ban_index
          key: rating_index
      key_len: 5
          ref: NULL
         rows: 10
        Extra: Using where

你应该研究这个手册页来获得输出的解释:https://dev.mysql.com/doc/refman/8.0/en/explain-output.html

我希望没有索引可以用于使用表达式的查询。

WHERE (BanMask & (1 << 2)) > 0

EXPLAIN 报告显示:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: RatingTable
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
        Extra: Using where; Using filesort

一般来说,如果比较运算符左侧的索引列在表达式或函数中被引用,则不能使用索引。它必须是“裸”列。

当您按索引的排序顺序搜索组合在一起的值时,索引会起作用。您的示例搜索 BanMask 中的每 4 个值,即那些在 4 的位置集中具有该位的值。这些值不是连续的,它们是分散的。 MySQL 不会使用索引来搜索整个范围内的值,因为最终这将与扫描整个表一样昂贵。

至于你的第二个问题,关于在(Rating DESC, BanMask ASC) 上添加索引,答案是它可能有助于避免文件排序。但它无助于搜索 BanMask。

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: RatingTable
   partitions: NULL
         type: index
possible_keys: NULL
          key: Rating
      key_len: 10
          ref: NULL
         rows: 10
        Extra: Using where

【讨论】:

  • 谢谢!我使用了解释,我的测试证实了你所写的一切。位操作使用索引中断。添加索引(Rating、BanMask)可以减少执行时间。如果我希望对其进行优化,看起来对于每个 Ban 原因而不是掩码都有一个单独的列是最简单和可靠的方法。
【解决方案2】:

这是BanMask &gt; 0 的解决方法,但不是其他查询这是不同的。

不要让BanMask 有一堆不同的非零值,而是有一个表示禁止的值。

一种方法是让另一列只是真/假,然后做

WHERE banned = 1  ORDER BY Rating DESC  LIMIT ..
INDEX(banned, Rating)  -- in _this_ order

其中的一个变体是有一个“生成”列(如果您有足够新的 MySQL/MariaDB 版本),它根据 BanMask 计算真/假。

上面的真正好处是LIMIT可以被看到和使用。也就是说,只需要查看 10 行。所有其他解决方案都必须扫描很多行,可能是整个表。

以下是一些通用规则:

  • 索引的第一个列应使用=进行测试。
  • 一旦您测试了具有范围 (BanMask &gt; 0) 的列,就可以使用该列,但没有其他列有用。
  • 隐藏函数中的列(在您的情况下,&amp; 是一个函数),使得无法在索引中使用该列。

对于原始问题并且没有生成列,那么我希望 INDEX(Rating) 是唯一有用的索引。由于您要求所有列 (SELECT *),因此将其扩展为“覆盖”是不切实际的。

【讨论】:

  • 谢谢!减少可能包含我正在寻找的值的行数的想法很有趣
猜你喜欢
  • 2012-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-18
  • 1970-01-01
  • 2013-01-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多