【问题标题】:MySQL join is scanning the table rather than using the indexMySQL join 是扫描表而不是使用索引
【发布时间】:2020-09-19 01:18:05
【问题描述】:

我有以下表格:

显示创建表 access_token_status;

CREATE TABLE `access_token_status` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `status` varchar(10) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `status` (`status`),
  KEY `idx_access_token_status_status_lookup_2` (`status`,`id`),
  KEY `idx_access_token_status_status_lookup_1` (`id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

显示创建表用户;

CREATE TABLE `user` (
  `id` varchar(17) NOT NULL,
  `short_lived_access_token_status_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_user_status_lookup_1` (`id`,`short_lived_access_token_status_id`),
  KEY `idx_user_status_lookup_2` (`short_lived_access_token_status_id`,`id`),
  KEY `ix_user_short_lived_access_token_status_id` (`short_lived_access_token_status_id`),
  CONSTRAINT `user_ibfk_1` FOREIGN KEY (`short_lived_access_token_status_id`) REFERENCES `access_token_status` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

显示创建表帐户;

CREATE TABLE `account` (
  `id` varchar(17) NOT NULL,
  `user_id` varchar(17) NOT NULL,
  `track` tinyint(1) NOT NULL,
  `estimated_time_to_regain_access` int(11) NOT NULL,
  `media_list_fetched_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_account_next_fetch_lookup` (`user_id`,`estimated_time_to_regain_access`,`track`,`media_list_fetched_at`),
  CONSTRAINT `account_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
  CONSTRAINT `account_chk_1` CHECK ((`track` in (0,1)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

当我尝试解释以下查询时

explain select a.*
        from account a
        inner join user u
        on a.user_id = u.id
        inner  join access_token_status as s
        on u.short_lived_access_token_status_id = s.id and s.status = 'valid'
        where
            u.short_lived_access_token_status_id = 3
            and a.estimated_time_to_regain_access = 0
            and a.track = true
            and a.media_list_fetched_at > '2020-05-30 12:31:01'
        limit 1
        for update of a SKIP LOCKED

我得到这个输出:

# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
'1', 'SIMPLE', 's', NULL, 'const', 'PRIMARY,status,idx_access_token_status_status_lookup_2,idx_access_token_status_status_lookup_1', 'PRIMARY', '4', 'const', '1', '100.00', NULL
'1', 'SIMPLE', 'a', NULL, 'index', 'idx_account_next_fetch_lookup', 'idx_account_next_fetch_lookup', '80', NULL, '2', '50.00', 'Using where; Using index'
'1', 'SIMPLE', 'u', NULL, 'eq_ref', 'PRIMARY,idx_user_status_lookup_1,idx_user_status_lookup_2,ix_user_short_lived_access_token_status_id', 'PRIMARY', '70', 'media_meta.a.user_id', '1', '100.00', 'Using where'

似乎某些表没有使用索引。这是我的情况的一个问题,因为扫描的行被锁定并且其他查询将跳过它们(由于 SKIP LOCKED 需要确保查询不会相互阻塞)

我不确定我缺少什么索引,或者我是否需要更改查询中的某些内容

【问题讨论】:

  • 请注意,没有 ORDER BY 的 LIMIT 是毫无意义的
  • 我会尝试更改索引的顺序,以便 user_id 出现在末尾​​span>
  • 另外,我想不出一个 id 不是整数的充分理由
  • 在我的用例中顺序无关紧要,我没有提供这样的细节以避免分心。但这会影响执行计划吗?你对索引是正确的,这也是可能超出问题范围的业务逻辑,除非你认为这正在改变执行计划(我确实尝试过整数 id,但计划没有改变)我确实尝试过添加一个新索引user_id 最后,确实有效,谢谢
  • 如果您认为问题已解决,请随时回答问题并接受您自己的回答,否则删除问题。

标签: mysql join query-optimization sql-execution-plan


【解决方案1】:

在您的查询中,您使用“u.short_lived_access_token_status_id = 3”,因此与“s”的连接看起来要么是不可能的(如果 s.id = 3 的 s.status 不是“有效”)或多余的(如果是) .除非您从 s 中获取其他一些列。

现在让我们看看“u”在哪里使用:

on a.user_id = u.id
...
    on u.short_lived_access_token_status_id = ...

因此,您正在使用基于 u.short_lived_access_token_status_id = 3 的主要选择标准,并且您需要从中获取 u.id。所以你的idx_user_status_lookup_2 应该是一个覆盖索引。

为什么不呢?可能是因为表太小以至于无关紧要,或者因为与 S 的联接干扰了优化器(您会注意到表 u 被评估为第三)。

如果可能,请尝试使用 s 删除连接,看看会发生什么。

【讨论】:

    【解决方案2】:

    似乎是“过度规范化”。

    我认为将status 从表格移到另一个表格没有任何优势。这样做会使优化复杂化,并且不会节省太多空间(如果有的话)。

    实际上,您可以使用ENUM('invalid', 'valid', ...) NOT NULL,这将占用 1 个字节而不是 INT 的 4 个字节。

    在 InnoDB 中,当你有 PRIMARY KEY(id) 时,你有一个由 id 排序的 BTree。因此,任何以id 开头的二级索引开始 都是多余的。另请注意,根据定义,PRIMARY KEYUNIQUE

        where
            u.short_lived_access_token_status_id = 3
            and a.estimated_time_to_regain_access = 0
            and a.track = true
            and a.media_list_fetched_at > '2020-05-30 12:31:01'
    

    两个都想用

    INDEX(short_lived_access_token_status_id, ...)
    

    但由于基数低,这似乎不太可能,或者

    INDEX(estimated_time_to_regain_access, track,   -- in either order
          media_list_fetched_at)    -- last, since it is a 'range'
    

    这个“覆盖”索引会更好:

    INDEX(estimated_time_to_regain_access, track,
          media_list_fetched_at, user_id)
    

    【讨论】:

      猜你喜欢
      • 2021-09-04
      • 1970-01-01
      • 2021-08-12
      • 1970-01-01
      • 1970-01-01
      • 2021-03-19
      • 2023-03-29
      • 2020-02-14
      相关资源
      最近更新 更多