具有多个共享列的 MySql 索引策略
Posted
技术标签:
【中文标题】具有多个共享列的 MySql 索引策略【英文标题】:MySql Indexing Strategy With Multiple Shared Columns 【发布时间】:2017-01-24 17:00:35 【问题描述】:我们有一个数据库表,用于存储访问者的浏览器数据,按多个不同的子类型细分。为简单起见,让我们使用下面的表模式。查询基本上将在任何单个 id 列、metric 列、timestamp 列(存储为自纪元以来的秒数)以及 device、browser 或 os 列之一。
我们将对该表进行星型与雪花模式的性能测试(其中所有 id 都进入一个列,但随后添加了一个附加列 id_type
以确定它是哪种类型的标识符),但是只要星型模式(现在就是这样)在雪花性能的 80% 以内,我们就会保留它,因为它会使我们的加载过程更容易。然而,在我这样做之前,我想确保索引在星型架构上进行了优化。
create table browser_data (
id_1 int,
id_2 int,
id_3 int,
id_4 int,
metric varchar(20),
browser varchar(20),
device varchar(20),
os varchar(20),
timestamp bigint
)
最好只在 id 列上创建单独的索引,还是在这些索引中也包括 metric
和 timestamp
列?
【问题讨论】:
为每个子类型设置单独的列真的很麻烦吗?对于事实表模式来说,能够维护适当的外键关系似乎是可取的。我意识到 4 字节的标准列最终可能会浪费空间。 @GordonLinoff 这不会是世界末日,但它只会使我们获取数据的 ETL 过程更加复杂。这就是为什么只要性能相似,就值得权衡保持流程不变 【参考方案1】:不要规范化“连续”值,例如 DATETIME
、FLOAT
、INT
。 不要将值留在主表中。
当您将值移动到其他表时,尤其是“雪花”,它会根据值进行查询,速度介于稍慢和很多之间。当您需要过滤多个不在主表中的指标时,尤其会发生这种情况。由于“雪花”或“过度规范化”,这些中的任何一个都表现得很差:
WHERE a.x = 123 AND b.y = 345
ORDER BY a.x, b.y
至于要创建什么索引——这完全取决于您需要执行的查询。所以,我强烈建议你根据你暂定的CREATE TABLEs
勾勒出可能的SELECTs
。
INT
是 4 个字节。 TIMESTAMP
是 5,FLOAT
是 4,等等。也就是说,规范化这样的东西在空间上也是低效的。
更多
在执行JOINs
时,优化器几乎总是从一个表开始,然后转到另一个表,依此类推。(请参阅“嵌套循环连接”。)
例如(基于上面的“代码”),当 2 列被规范化并且您正在测试值时,您手头没有两个 ids
,您只有两个值。这使得查询执行非常低效。对于
SELECT ...
FROM main
JOIN a USING(a_id)
JOIN b USING(b_id)
WHERE a.x = 123 AND b.y = 345
以下很可能是“执行计划”:
-
进入
a
查找x=123 的行;获取这些行的id(s)
。这可能包括许多尚未被b.y
过滤的行。 a
需要INDEX(x)
返回main
表,查找具有这些ID 的行。 main
需要 INDEX(a_id)
。同样,可能会拖出多余的行数。
只有现在,你才能到b
(使用b_id
)来检查y=345
;扔掉你一直拖着的不必要的行。 b
需要INDEX(b_id)
请注意我对“拖拉”的评论。盲目使用*
(在SELECT *
中)会增加问题——在执行这些步骤时,所有的列都被拖着走。
另一方面...如果x
和y
在main
表中,那么代码的工作方式如下:
WHERE main.x = 123
AND main.y = 345
只需要INDEX(x,y)
(任意顺序)。它可以快速准确地找到所需的行。
在ORDER BY a.x, b.y
的情况下,它不能在any 表上使用any 索引。因此查询必须创建一个 tmp 表,对其进行排序,然后按所需顺序传递行。
但如果x
和y
在同一个表中,那么INDEX(x,y)
(按此顺序)可能对ORDER BY x,y
有用并避免使用tmp 表和排序。
对于单个表,优化器可能使用WHERE
的索引,或者它可能使用ORDER BY
的索引,具体取决于月相。在某些情况下,一个索引可以同时用于两者——这是最佳选择。
另一个注意事项:如果您也有LIMIT 10
,...如果避免使用sort
,则只需查看10 行,而不是WHERE
中的整个集合。
【讨论】:
每个查询将使用至少 id 列之一,绝对是度量列,绝对是时间戳列。我想我真正的问题是,如果我对每个 ID 列都有不同的复合索引,唯一的区别是前导索引列,这是否是不好的做法。在每个 id 列上有一个单列索引,然后在其余列上创建一个复合索引会更好吗? 你在挤我的论文;查看我添加的文字。 还有一点需要注意:mysql 很少为每个SELECT
使用一个以上的索引。 (否则这样做根本没有效率。)
如果你有INDEX(a,b)
,不要加INDEX(a)
。但是INDEX(b)
id 不同。 More discussion以上是关于具有多个共享列的 MySql 索引策略的主要内容,如果未能解决你的问题,请参考以下文章