我有以下表。
SHOW CREATE TABLE 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
和
SHOW CREATE TABLE user;
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
和
SHOW CREATE TABLE account;
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是需要的,以确保查询不被相互阻塞)。
我不知道我漏掉了什么索引,或者我是否需要改变查询中的一些内容。
在你的查询中,你使用了 "u.short_lived_access_token_status_id = 3",所以用 "s "连接看起来要么是不可能的(如果s.status for s.id = 3不是 "有效的"),要么是多余的(如果是)。除非你要从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的连接,看看会发生什么。
似乎是 "过度正态化"。
我看不出移动 status
的表到另一个表中。 这样做会使优化变得复杂,而且不会节省很多空间,如果有的话。
事实上,您可以使用一个 ENUM('invalid', 'valid', ...) NOT NULL
,这将占用1个字节,而不是4个字节的 INT
.
在InnoDB中,当你有 PRIMARY KEY(id)
你有一个BT树,由 id
. 因此,任何二级指数 开始 与 id
是多余的。 还请注意,一个 PRIMARY KEY
根据定义,是。UNIQUE
.
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)