两个关系表的MySQL数据库架构

时间:2019-03-22 20:31:30

标签: mysql performance

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

直观地表示此数据结构:

- 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)

我想要实现的是两个表的数据库架构,一个表用于课程,一个表用于主题。想法是让一个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::根据表结构,使用索引或复合索引可以提高查询性能。
  • 不要试图在一个大查询中获取所有内容:您绝对可以,但是可以对所需数据部分进行单独查询(在我的示例中,一个查询可以获取一个主题的所有主题当然,一个获得该课程的所有视频,一个获得该课程的资源)在代码的组织性和可读性方面得到回报。

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

3 个答案:

答案 0 :(得分:1)

以最简单的形式,假设不存在多对多关系:

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 = ?

答案 1 :(得分: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)
    • 标题
    • 网址

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

  • themes_videos
    • 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), 然后,您可以检索相关数据(从多对多模式) 以下查询(您一个接一个地执行):

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查询获取所有数据并不总是一个好主意。 您只是使您的代码和架构过于复杂。 执行几个查询并不是世界末日。 您应该避免的是循环运行查询 (例如:为每个主题选择相关的视频)。

答案 2 :(得分:-1)

Table Structure

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