【发布时间】:2021-10-29 01:32:50
【问题描述】:
Mariadb 没有完全使用复合索引。快选和慢选都返回相同的数据,但是解释表明慢选只使用了 ix_test_relation.entity_id 部分,不使用 ix_test_relation.stamp 部分。
我尝试了很多情况(内连接、with、from),但无法让 mariadb 将索引的两个字段与递归查询一起使用。我知道我需要告诉 mariadb 以某种方式实现递归查询。
请帮助我优化使用递归查询的慢速选择,使其速度与快速选择相似。
关于任务的一些细节...我需要查询用户活动。一个用户活动记录可能涉及多个实体。实体是分层的。我需要查询某些父实体的用户活动和指定邮票范围的所有子实体。为简化演示,Stamp 从 TIMESTAMP 简化为 BIGINT。可能有很多(100 万)个实体,每个实体可能与很多(100 万)个用户活动条目相关。实体层次结构深度预计为 10 级深。我假设使用的标记范围将用户活动记录的数量减少到 10-100。我对模式进行了非规范化,将标记从 test_entry 复制到 test_relation 以便能够将其包含在 test_relation 索引中。
我使用 10.4.11-Mariadb-1:10:4.11+maria~bionic。 如果需要,我可以升级或修补或任何 mariadb,我可以完全控制构建 docker 映像。
架构:
CREATE TABLE test_entity(
id BIGINT NOT NULL,
parent_id BIGINT NULL,
CONSTRAINT pk_test_entity PRIMARY KEY (id),
CONSTRAINT fk_test_entity_pid FOREIGN KEY (parent_id) REFERENCES test_entity(id)
);
CREATE TABLE test_entry(
id BIGINT NOT NULL,
name VARCHAR(100) NOT NULL,
stamp BIGINT NOT NULL,
CONSTRAINT pk_test_entry PRIMARY KEY (id)
);
CREATE TABLE test_relation(
entry_id BIGINT NOT NULL,
entity_id BIGINT NOT NULL,
stamp BIGINT NOT NULL,
CONSTRAINT pk_test_relation PRIMARY KEY (entry_id, entity_id),
CONSTRAINT fk_test_relation_erid FOREIGN KEY (entry_id) REFERENCES test_entry(id),
CONSTRAINT fk_test_relation_enid FOREIGN KEY (entity_id) REFERENCES test_entity(id)
);
CREATE INDEX ix_test_relation ON test_relation(entity_id, stamp);
CREATE SEQUENCE sq_test_entry;
测试数据:
CREATE OR REPLACE PROCEDURE test_insert()
BEGIN
DECLARE v_entry_id BIGINT;
DECLARE v_parent_entity_id BIGINT;
DECLARE v_child_entity_id BIGINT;
FOR i IN 1..1000 DO
SET v_parent_entity_id = i * 2;
SET v_child_entity_id = i * 2 + 1;
INSERT INTO test_entity(id, parent_id)
VALUES(v_parent_entity_id, NULL);
INSERT INTO test_entity(id, parent_id)
VALUES(v_child_entity_id, v_parent_entity_id);
FOR j IN 1..1000000 DO
SELECT NEXT VALUE FOR sq_test_entry
INTO v_entry_id;
INSERT INTO test_entry(id, name, stamp)
VALUES(v_entry_id, CONCAT('entry ', v_entry_id), j);
INSERT INTO test_relation(entry_id, entity_id, stamp)
VALUES(v_entry_id, v_parent_entity_id, j);
INSERT INTO test_relation(entry_id, entity_id, stamp)
VALUES(v_entry_id, v_child_entity_id, j);
END FOR;
END FOR;
END;
CALL test_insert;
慢速选择(> 100ms):
SELECT entry_id
FROM test_relation TR
WHERE TR.entity_id IN (
WITH RECURSIVE recursive_child AS (
SELECT id
FROM test_entity
WHERE id IN (2, 4)
UNION ALL
SELECT C.id
FROM test_entity C
INNER JOIN recursive_child P
ON P.id = C.parent_id
)
SELECT id
FROM recursive_child
)
AND TR.stamp BETWEEN 6 AND 8
快速选择(1-2ms):
SELECT entry_id
FROM test_relation TR
WHERE TR.entity_id IN (2,3,4,5)
AND TR.stamp BETWEEN 6 AND 8
更新 1
我可以用更短的例子来证明这个问题。
在临时表中显式存储所需的 entity_id 记录
CREATE OR REPLACE TEMPORARY TABLE tbl
WITH RECURSIVE recursive_child AS (
SELECT id
FROM test_entity
WHERE id IN (2, 4)
UNION ALL
SELECT C.id
FROM test_entity C
INNER JOIN recursive_child P
ON P.id = C.parent_id
)
SELECT id
FROM recursive_child
尝试使用临时表(如下)运行选择。 Select 仍然很慢,但现在与快速查询的唯一区别是 IN 语句查询表而不是内联常量。
SELECT entry_id
FROM test_relation TR
WHERE TR.entity_id IN (SELECT id FROM tbl)
AND TR.stamp BETWEEN 6 AND 8
【问题讨论】:
-
我考虑过将复合索引切换为 stamp,entity_id,但在这种情况下,相对于实体数量而言,索引对应于 O(n) 而不是 O(1)。这意味着此查询会随着实体的增加而变慢,这不应该发生
-
请提供
EXPLAIN SELECT ... -
如果
stamp是TIMESTAMP,请保持原样。BETWEEN 6 AND 8闻起来像IN(6,7,8),TIMESTAMP不太可能发生这种情况。 -
拥有 both (stamp, entity_id) 和 (entity_id, stamp) 不会有什么坏处——这样优化器可以动态地选择它们之间基于数据集。
标签: indexing mariadb query-optimization sql-execution-plan composite-key