【问题标题】:MySQL database Schema for two relational tables两个关系表的 MySQL 数据库模式
【发布时间】:2019-03-28 21:29:04
【问题描述】:

我正在尝试为特定数据结构考虑性能最高的数据库架构。有两个主要实体:课程主题课程主题的集合。 主题具有视频资源视频总时间等字段。

直观地表示这个数据结构:

- Course
|_ ID: 12345
|_ Themes: [A, B] (an array of UIDs)

- Theme A
  |_ Courses: [12345,67890] (an array of UIDs)
  |_ Videos: [1,2,3,4,5,7] (an array of UIDs)
  |_ Resources: [10,11,12] (an array of UIDs)
  |_ Video Total Time: 10000 (probably stored as seconds as tinyint field)
- Theme B
  |_ Courses: [12345,98765] (an array of UIDs)
  |_ Videos: [5,6,7,8] (an array of UIDs)
  |_ Resources: [12,13,14] (an array of UIDs)
  |_ Video Total Time: 20000 (probably stored as seconds as tinyint field)

我想要实现的是两个表的数据库架构,一个用于Courses,一个用于Themes。想法是让 MySQL 查询获取 Course 并将 Themes 中的所有字段分组。换句话说,当我得到 MySQL 查询的结果时,(使用 PHP)我会得到一个这样的数组或对象:

Array(
  'ID' => 12345
  'themes' => [A,B]
  'videos' => [1,2,3,4,5,6,7,8]
  'resources' => [10,11,12,13,14]
  'video_total_time' => 30000
)

所以,关键是它们是两个关系数据库。当我向数据库发送请求视频数据的查询时,我需要从所有主题中提取数据,并将它们合并在一起。

由于我不是 SQL / MySQL 方面的专家,所以我在尝试弄清楚的同时尝试了解一点:

1) 这两个实体的最佳数据库模式是什么?课程和主题?专门考虑性能

2) 我可以全部使用 SQL 获取最终数据吗?或者我应该从数据库中提取一些数据,然后用 PHP 解析数据?什么通常更快?

3) 存储 UID 数组的最佳方式是什么?作为字符串?还是有更好的存储方式?

这样做的主要目标是性能。我在不同的数据库架构中拥有此类数据,并与数千种其他类型的数据(WP 数据库、wp_posts / wp_postmeta 表)合并,但现在获取我需要的信息真的很慢。

非常欢迎任何提示和建议!


编辑:已解决!

决定哪个答案最适合我的需求是一个艰难的决定,因为@TimMorton 和@PaulSpiegel 的答案将我们引向相同的道路,但方法略有不同。 Tim 的回答非常有助于理解如何正确设计数据库模式、考虑多对多关系以及如何组织查询。但由于这个问题的主要焦点是提高性能,因此 Paul 的回答更侧重于这一点,包括有关主键和索引的具体细节(这对于提高查询性能至关重要)。

无论如何,我学到了很多关于设计数据库模式的知识。以下是我学到的经验教训:

  • 不要试图将所有内容都放入同一个表中:在定义您需要的表之前正确识别实体至关重要。我从两个表开始,分别用于视频和主题。但事实证明,适合我的规范的数据库架构包括视频和资源表。
  • 不要将数组存储到列中:使用适当的策略来定义实体之间的关系。如果您有一对一或一对多的关系,请使用实体 ID 和外键。如果您有多对多关系,那么正确的设计模式是创建一个专用表,仅用于创建实体之间的关系。这将允许您在查询中使用 JOIN 子句来将所有数据放在一起。
  • 当您考虑性能时,请考虑 INDEX:根据您的表结构,使用索引或复合索引都可以提高查询性能。
  • 不要试图在一个大查询中获取所有内容:您当然可以,但是对您需要的数据部分进行单独查询(在我的示例中,一个查询获取课程的所有主题,一个查询获得课程的所有视频,获得课程资源的人)通过代码组织和可读性获得回报。

我不知道我对以上所有内容是否正确,但这是我到目前为止所学到的。希望这对其他人也有帮助。

【问题讨论】:

  • 将其视为两个表是错误的。我看到了主题、课程、视频和资源。一个线索是,任何时候你看到一个数组,想想表。例如,视频:视频将具有 id 、标题和运行时间。你不需要一个叫做视频总时间的字段;它只会在您的查询中相加。
  • 嘿@TimMorton 感谢您的评论。所以你认为获取我需要的所有数据的最有效的方法是将信息分解到多个表中?为主题设置 video_total_time 字段的想法是更快地获取此信息。但是由于我是 SQL / MySQL 的新手,也许在数据库中查询主题的所有视频,并且他们对所有视频时间进行总和,不会像我想的那么慢?
  • is-storing-a-delimited-list-in-a-database-column-really-that-bad。然后你可能会意识到你正在设计一个性能灾难。
  • 嘿@PaulSpiegel 感谢您的链接,非常有用。就像我说的,我是设计合适的数据库模式的新手。您将如何处理这些关系字段?我应该为每个组合建立一个关系表吗? IOW:一张表用于课程 主题,一张用于主题 视频,一张用于主题 资源?
  • 规范化模式每个实体有一个表,每个多对多关系一个表。一对多关系通过子表中的引用(外键)解决(因此这些关系没有额外的表)。据我所知,您没有多对多关系。所以你需要四个表:coursesthemesvideoressources

标签: mysql performance


【解决方案1】:

创建架构

第 1 步:识别实体及其属性

  • 课程(ID、标题、描述)
  • 主题(ID、标题、描述)
  • 视频(ID、标题、描述、持续时间)
  • 资源(ID、标题、网址)

第 2 步:识别关系

  • 主题 => 课程
  • 视频 => 主题
  • 资源 => 主题

第 3 步:创建表

  • 课程
    • ID (PK)
    • 标题
    • 说明
  • 主题
    • ID (PK)
    • course_id (FK)
    • 标题
    • 说明
  • 视频
    • ID (PK)
    • theme_id (FK)
    • 标题
    • 说明
    • 持续时间
  • 资源
    • ID (PK)
    • theme_id (FK)
    • 标题
    • 网址

如果主题可以共享视频资源,那么这将是多对多的关系。 在这种情况下,您需要为这些关系使用单独的表。 从videosressources 中删除theme_id 列并添加下表:

  • 主题视频
    • theme_id (PK) (FK)
    • video_id (PK) (FK)
  • themes_ressources
    • theme_id (PK) (FK)
    • ressource_id (PK) (FK)

在这里,您应该在(theme_id, video_id)(theme_id, ressource_id) 上定义复合主键。 同时在(video_id, theme_id)(ressource_id, theme_id) 上创建反向索引。

检索数据

假设您知道课程的 ID(即 123), 然后您可以检索相关数据(来自 many-to-many 模式) 使用以下查询(您一一执行):

select c.*
from courses c
where c.id = 123;

select t.*
from themes t
where t.course_id = 123;

select distinct v.*
from themes t
join themes_videos tv on tv.theme_id = t.id
join videos v on v.id = tv.video_id
where t.course_id = 123;

select distinct r.*
from themes t
join themes_ressources tr on tr.theme_id = t.id
join ressources r on r.id = tr.ressource_id
where t.course_id = 123;

然后从 PHP 中检索到的数据组成您的数组/对象。

性能

尝试使用单个 SQL 查询获取所有数据并不总是一个好主意。 你只是让你的代码和模式太复杂了。 执行几个查询并不是世界末日。 您应该避免的是在循环中运行执行查询 (例如:为每个主题选择相关视频)。

【讨论】:

  • 关于性能的好提示。 可以在一个查询中获得所有信息(假设 MySQL 具有聚合函数),但它会是......毛茸茸的。最好保持简单,以便您可以在代码中为下一个人解释它。
  • @paulSpiegel 感谢您的回复,非常有帮助!我可以问一些关于多对多表的说明吗?例如,在themes_videos 中,您说theme_idvideo_id 都是主键和外键,对吧?那么,我是否应该将表设置为将这些添加为主键,并且还创建复合主键?另外,我听说反向索引仅适用于 MySQL >= 8.0。如果我使用的是旧版本(如 5.x),是否可以创建反向索引?
  • @DiegodeOliveira 我的意思是 一个 主键(每个表)有两列。例如。 PRIMARY KEY (theme_id, video_id), INDEX (video_id, theme_id)。使用反向索引我不是指DESC,而是反转列顺序。
  • @PaulSpiegel 哦,明白了!感谢您的解释!现在它对我来说更有意义了!
【解决方案2】:

以最简单的形式,假设没有多对多关系:

Course                Theme
--------              --------
CourseID <--+         ThemeId
Name        |         Name
            +------   CourseID
            |
            |      
            |         Video
            |         --------
            |         VideoID
            |         Name
            |         Length
            +------   CourseID
            |
            |
            |         Resource
            |         --------
            |         ResourceID
            |         Name
            +------   CourseID

在这种形式中,一个课程可以有很多主题、很多视频和很多资源;但每个主题、视频和资源只能有一个课程。

但是,我认为这不是你想要的。

我会更倾向于

                      Course             Theme
                      --------           --------
            +---->    CourseId    +--->  ThemeId
            |         Name        |      Name
            |         ThemeId ----+      
            |
            |      
            |         Video
            |         --------
            |         VideoID
            |         Name
            |         Length
            +------   CourseID
            |
            |
            |         Resource
            |         --------
            |         ResourceID
            |         Name
            +------   CourseID

这允许一门课程只有一个主题,但有许多视频和资源。这允许主题有多个课程。

但它仍然不太符合要求...

这个允许许多课程共享相同的主题,也可以有多个主题:

                      Course         Course_Theme      Theme
                      --------       ------------      --------
            +---->    CourseId <----- CourseId   +-->  ThemeId
            |         Name            ThemeId ---+     Name
            |         ThemeId       
            |
            |      
            |         Video
            |         --------
            |         VideoID
            |         Name
            |         Length
            +------   CourseID
            |
            |
            |         Resource
            |         --------
            |         ResourceID
            |         Name
            +------   CourseID

就目前而言,每门课程都可以有许多主题、视频和资源。 每个主题可以有很多课程。 每个视频和资源都属于一个课程(即,只能有一个课程)

如果一个视频或资源可以用于多个课程,那么您必须像我对主题所做的那样对其进行扩展。


根据评论,一切都是多对多。请注意,我在主题和视频之间以及主题和资源之间没有任何直接关系。我不认为它们是必要的。您应该能够通过课程获得所需的内容。

          Course         Course_Theme        Theme
          --------       ------------        --------
+---->    CourseId <---- CourseId                   
|         Name           ThemeId ----------> ThemeId
|                                            Name
|
|                        Course_Video        Video
|                        ------------        --------
+----------------------  CourseId                      
|                        VideoId ----------> VideoId
|                                            Name         
|                                            Length             
|                                                         
|                        Course_Resource     Resource
|                        ---------------     --------     
+----------------------- CourseId                        
                         ResourceId -------> ResourceId   
                                             Name         
                                             Url, etc.    

现在进行查询。尽管可以将聚合函数与 group by 一起使用,但我认为保持简单并一次只提取一个更有意义。

Themes per course
SELECT T.* 
FROM COURSE C
INNER JOIN COURSE_THEME CT ON CT.COURSEID=C.COURSEID
INNER JOIN THEME T ON CT.THEMEID=T.THEMEID 
WHERE {insert your search conditions on course}

or, if you know CourseId:

SELECT T.*
FROM THEME T
INNER JOIN COURSE_THEME CT ON T.THEMEID = CT.THEMEID
WHERE CT.COURSEID = ?


likewise,

Videos per course
SELECT V.*
FROM COURSE C
INNER JOIN COURSE_VIDEO CV ON CV.COURSEID=CV.COURSEID
INNER JOIN VIDEO ON CV.VIDEOID=V.VIDEOID
WHERE {insert your search conditions on course}

or, if you know the CourseId:

SELECT V.*
FROM VIDEO V
INNER JOIN COURSE_VIDEO CV ON CV.VIDEOID = V.VIDEOID
WHERE CV.COURSEID = ?

to select the sum of the video lengths per course,

SELECT SUM(LENGTH) AS TOTAL
FROM VIDEO
INNER JOIN COURSE_VIDEO CV ON CV.VIDEOID = V.VIDEOID
WHERE CV.COURSEID = ?
GROUP BY CV.COURSEID

Now, the tricky part is videos per theme.  I am making an assumption here:  the set of videos per theme is the same as the set of videos per course per theme.

The long way around:

SELECT V.*
FROM VIDEO V
INNER JOIN COURSE_VIDEO CV ON VIDEO.VIDEOID = CV.VIDEOID
INNER JOIN COURSE C ON COURSEID = CV.COURSEID
INNER JOIN COURSE_THEME CT ON C.COURSEID = CT.COURSEID
INNER JOIN THEME T ON CT.THEMEID = T.THEMEID
WHERE THEMEID = ?

Blech. You can cut out the middlemen:

SELECT V.*
FROM VIDEO V
INNER JOIN COURSE_VIDEO CV ON VIDEO.VIDEOID = CV.VIDEOID
INNER JOIN COURSE_THEME CT ON CV.COURSEID = CT.COURSEID
WHERE CT.THEMEID = ?

当您将表标准化后,您可以从您选择的任何起点获取任何信息。 FWIW,你的例子是一个相当复杂的例子,因为一切都是多对多的关系。


更新

即使我以课程为根,即使主题是根,也没有太大变化:

          Theme          Course_Theme        Course
          --------       ------------        --------
+---->    ThemeId <----  ThemeId                   
|         Name           CourseId ---------> CourseId
|                                            Name
|
|                        Theme_Video         Video
|                        ------------        --------
+----------------------  ThemeId                      
|                        VideoId --------->  VideoId
|                                            Name         
|                                            Length             
|                                                         
|                        Theme_Resource      Resource
|                        --------------      --------     
+----------------------- ThemeId                        
                         ResourceId ------>  ResourceId   
                                             Name         
                                             Url, etc.    

在这种配置下,课程通过ThemeId有视频和资源,即:

SELECT V.*
FROM COURSE_THEME CT 
INNER JOIN VIDEO_THEME VT ON VT.THEMEID = CT.THEMEID
INNER JOIN VIDEO V ON V.VIDEOID = VT.VIDEOID
WHERE CT.THEMEID = ?

【讨论】:

  • TimMorton 太棒了,我真的很喜欢你详细说明一切的方式。但是@PaulSpiegel 的回答对主题是正确的。它们实际上是拼图的主要部分。主题是视频和资源的集合。课程是主题的集合。在这个项目中,您通过选择一组主题来构建课程。在这里,您将视频和资源直接连接到课程。对不起,这是我的错,我应该做得更好,解释一切。您能否根据这些要求更新您的答案?
【解决方案3】:

Table Structure

制作如图所示的表格,并使用输入/输出的 json 编码/解码时间。在查询中,您可以从表中获得总时间。

【讨论】:

  • 我个人会更规范化它。您将外键保存在 varchar 中,大概是 csv?这在关系数据库中根本没有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-03
  • 1970-01-01
  • 2016-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-24
相关资源
最近更新 更多