当所有列都可能用于不同的搜索查询时,应该索引哪些列?

Vis*_*ent 5 mysql innodb mariadb index database-design

背景

我正在为目前位于四个不同城市(未来可能会扩展)的电影院连锁网站开发一个网站。他们为所有城市使用相同的单一数据库网站,这意味着我必须在某些表中有一列,其中包含每行所属城市的 ID。

现在我有三个不同的表:

  • Cinemas - 包含每个城市的电影院(ID 和名称)。
  • Movies - 包含所有已经/将在电影院放映的电影。
  • Showtimes - 包含所有城市所有电影的所有放映时间。

Showtimes表的结构如下:

Column Name   | Column Type  | Description
--------------+--------------+---------------
ID            | BIGINT       | (Primary) Unique ID for each showtime (perhaps unnecessary?)
CinemaID      | TINYINT      | Foreign key bound to Cinemas.ID
MovieID       | BIGINT       | Foreign key bound to Movies.ID
Showtime      | DATETIME     | At what date and time the movie will show 

(will contain multiple rows for each movie, i.e. one row for each showtime)
Run Code Online (Sandbox Code Playgroud)

如何使用此表

网站用户必须能够:

  • 查看所选城市中所有当前/即将上映的电影和放映时间(按日期排序)。

    示例查询(后端):

    SELECT MovieID, Showtime FROM Showtimes WHERE CinemaID = ? ORDER BY Showtime
    
    Run Code Online (Sandbox Code Playgroud)
  • 选择一部电影并仅查看该特定标题的所有放映时间(在所选城市中)。

    示例查询:

    SELECT Showtime FROM Showtimes WHERE CinemaID = ? AND MovieID = ? ORDER BY Showtime
    
    Run Code Online (Sandbox Code Playgroud)
  • 选择一天并仅查看当天的所有电影和放映时间(在所选城市中)。

    示例查询:

    SELECT MovieID, Showtime FROM Showtimes WHERE CinemaID = ? AND (Showtime BETWEEN [date 12:00 AM] AND [date 12:00 PM])
    
    Run Code Online (Sandbox Code Playgroud)

所以很自然地我决定我需要为列创建索引。

问题

我遇到的问题是决定/确定如何正确索引列。每列一个索引似乎相当昂贵[1] [2]所以我开始研究复合索引,这似乎是正确的选择,但也导致了更多的混乱。

从我的理解(根据我读过),你应该选择的顺序添加列索引,使得最有选择性的(我猜手段最基数最独特/?)列第一的复合索引[3](在我的例子中就是Showtime列)。唯一的问题是,如果第一列包含在搜索查询[4] [5] 中,则该索引只能由数据库使用,而它目前不在我的任何一个查询中。

为了涵盖所有使用场景,我应该对我的列应用什么样的索引?(最后一个场景可以省略,但前两个是必须的)

对于某些列,我应该对所有列使用复合索引,还是需要为每列使用单独的索引?

此表最多每周更新几次以添加新的放映时间。

脚注

1 MySQL 索引 - 最佳实践是什么?

2 索引表中的每一列

3 索引中列的顺序有多重要?(题)

4 索引中列的顺序有多重要?(#2 最高投票答案)

5 什么时候应该使用复合索引?

Wil*_*ema 5

复合主键

我会将主键定义为(CinemaID, MovieID, Showtime).

这 3 列唯一地标识每一行,因此ID不需要单独的列。

综合(二级)指数

使用此 PK,查询所需的唯一附加索引是(CinemaID, Showtime).

为什么有这些索引?

考虑使用索引的一个好方法是将它们视为电子表格中列的顺序。

想象一个电子表格,(CinemaID, MovieID, Showtime)它按每列连续排序。

您的所有查询都已CinemaID存在,这意味着您可以快速找到该电子表格的“部分” CinemaID。然后,对于按 搜索的查询MovieID,您可以轻松地在第二列中找到MovieID与搜索值匹配的“小节” 。

由于第 3 列Showtime也已排序,您可以想象在该电影院中找到该电影的所有放映时间是多么快速和轻松。DBMS 以类似的方式做事,可以极快地检索这些结果。

至于您的其他查询,它们都以某种方式开始CinemaID然后使用Showtime。他们还需要MovieID在他们的结果中。

所以,(CinemaID, Showtime)索引已经涵盖了你。同样,CinemaID很容易找到电子表格的“部分”(类比),所有可能的放映时间(并且会有重复,假设有多个屏幕)将按顺序列出并且很容易搜索和/或按这些值排序。

更好的是,由于您的主键包括MovieID,该列包含在定义列之后的所有二级索引中(至少对于 MySQL InnoDB - 其他引擎也是如此,但不一定全部。)

想想这是我们二级索引“电子表格”中的第三列。列存在的原因是让主键的所有部分都可用于在必要时查找主表(也就是 InnoDB 中的聚集索引)。在这个简单的情况下,不需要查找,因此它更有效,因为它不需要双重查找。

仅使用此主键和单个二级索引,您应该在您列出的任何查询上获得出色的性能。

事后的想法

如果您同时在多个屏幕上播放电影,我认为这唯一标识每一行的假设可能不正确。如果您希望能够单独识别这些屏幕,那么我的解决方案不是最好的(我可以针对这种情况提供另一种解决方案,请告诉我。)


Ric*_*mes 5

WHERE CinemaID = ? ORDER BY Showtime  -- and
WHERE CinemaID = ? AND (Showtime BETWEEN [date 12:00 AM] AND [date 12:00 PM])  -- need:
INDEX(CinemaID, Showtime)

WHERE CinemaID = ? AND MovieID = ? ORDER BY Showtime  -- needs:
INDEX(CinemaID, MovieId, Showtime)  -- or
INDEX(MovieId, CinemaID, Showtime)
Run Code Online (Sandbox Code Playgroud)

假设三元组(MovieId、CinemaID、Showtime)是唯一的,我建议去掉id并拥有

PRIMARY KEY(CinemaID, MovieId, Showtime)
INDEX(CinemaID, Showtime)
Run Code Online (Sandbox Code Playgroud)

是否有主要部分是的WHERE情况MovieID=...

电影院 - 每个城市及其电影院的列表(ID 和名称):

SELECT Cinema, CinemaID FROM Cimemas;  -- (no index needed)
Run Code Online (Sandbox Code Playgroud)

电影 - 已经/将在电影院放映的电影列表。

SELECT DISTINCT MovieID FROM ShowTimes WHERE CinemaID=...
INDEX(CinemaID, MovieID)  -- already handled by my proposed PK
Run Code Online (Sandbox Code Playgroud)

放映时间 - 所有城市所有电影的所有放映时间列表。-- 这是一个巨大的输出;重新考虑对它的要求。也就是说,考虑客户端将用它做什么。

这些索引中的大多数可以通过研究索引手册复合索引推导出来

请注意,在我看到SELECTs.

“按选择性顺序排列到索引的列,使其最具选择性(我猜这意味着最独特/具有最多基数?)”--不。选择性不是设计复合索引的关键。=任何顺序从所有列开始。(我的食谱详细讨论了这个话题。)

“如果第一列包含在搜索查询中,则索引只能由表使用”——大多数情况下是正确的。注意我是如何推荐 2 个索引的(记住:PK 是一个索引)。在某些情况下,索引可用于GROUP BYORDER BY,而忽略WHERE; 但这些很少见。

“综合指数第一名...... Showtime”——将第一名置于综合指数中通常会适得其反DATETIME。特别是,您的第三个查询可以使用 的INDEX(CinemaID, Showtime),但不能使用 的两INDEX(Showtime, CinemaID)。很容易看出这一点:想想写出两个电影院和放映时间的列表。让一个列表首先在电影上排序(a la INDEX(cinema, time);让另一个按时间排序。想想哪个列表会将特定电影在一个时间范围内的所有行聚集(“聚集”)在一起。

如果ScreenID由于两个屏幕同时播放同一部电影,也有一个原因,则将其添加到PK上。但是,所有索引都需要重新考虑。

(对不起,Willem,我在阅读你的之前写了我的答案——我们说的几乎一样。)