优化数据库结构

Posted

技术标签:

【中文标题】优化数据库结构【英文标题】:Optimising Database Structure 【发布时间】:2011-11-30 19:25:01 【问题描述】:

我正在为我们的 VLE 开发一个奖励系统,它使用三种独立的技术 - javascript 用于大多数客户端/显示处理,php 用于与数据库通信,mysql 用于数据库本身。

我附上了我的“交易”表的三个屏幕截图。它的结构、一些示例记录及其详细信息的概述。

前提是工作人员会为表现良好的学生奖励积分。这可能意味着 30 名学生的班级一次获得积分。员工的积分上限为每周 300 点,目前约有 85 名员工正在访问该系统(这可能会增加)。

我现在的做法是,每笔“交易”都有一个“Giver_ID”(授予积分的员工)、一个“Recipient_ID”(获得积分的学生)、一个类别和一个原因。这样一来,每次有员工发出 30 分,我就将 30 行放入数据库中。

这在早期似乎可行,但在三周内我的数据库中已经有超过 12,000 笔交易。

此时它变得有点复杂。在“分配分数”页面(附上另一个屏幕截图)上,当老师点击他们的一个班级或搜索单个学生时,我希望显示学生的分数。我目前可以在我的系统上执行此操作的唯一方法是执行“SELECT * FROM 'transactions'”并使用以下 JS 将所​​有信息放入一个数组中:

var Points =  "Recipient_ID" : "0", "Points" : "0" ;

function getPoints (data) 
    for (var i = 0; i < data.length; i++) 
        if (Points[data[i].Recipient_ID]) 
            Points[data[i].Recipient_ID] = parseInt(Points[data[i].Recipient_ID]) + parseInt(data[i].Points);
         else 
            Points[data[i].Recipient_ID] = data[i].Points;
        
    

在内部登录系统时,这似乎运行得足够快。但是,在外部登录时,此过程大约需要 20 秒,因此在您单击/搜索几次之前不会显示学生的积分值。

我在我的 PHP 中使用以下代码来访问这些事务:

function getTotalPoints() 
    $sql = "SELECT * 
        FROM `transactions`";

    $res = mysql_query($sql);
    $rows = array(); 
    while($r = mysql_fetch_assoc($res)) 
        $rows[] = $r;
    

    if ($rows) 
        return $rows;
     else 
        $err = Array("err_id" => 1);
        return $err;
    

所以,我的问题是,我实际上应该如何处理这个问题?全文索引;可能是一个学生表,其总分值在每次输入交易时都会更新;大量交易(即多个学生在同一类别中获得相同分数)分组到单个数据库行中?这些都是我考虑过的事情,但我希望有人比我拥有更多的 DB 知识来提供启发。

示例记录

表结构

表格概览

分配积分界面

非常感谢。

【问题讨论】:

【参考方案1】:

根据 Tom 的建议,您可能需要考虑进一步规范化您的数据库。我假设你现在有 3 个表:

students (id, name, ...)

staff (id, name, ...)

transactions (id, student_id, staff_id, points, date, reason)

更规范化的表格使用更少数据的更多表:

students (id, name, ...)

staff (id, name, ...)

transactions (id, staff_id, points, date, reason)

transactions_students (transaction_id, student_id)

然后添加一个事务就变成了一个两步过程:首先创建一个事务记录,然后将多条记录插入到 transactions_students 中,每条记录都将事务链接到一个学生。请注意,您可以创建一个行为与用于选择的原始非规范化表完全相同的视图,例如:

CREATE VIEW vw_transactions AS SELECT transactions.*, transactions_students.student_id FROM transactions INNER JOIN transactions_students WHERE transactions_students.transaction_id = transactions.id

这将大大减少事务表中的记录数,并避免重复存储日期和原因。缺点是将交易链接到学生需要一个额外的连接 - 但如果您正确设置了外键和索引,这根本不是问题。

【讨论】:

谢谢 tdammers。您能否给我一个示例,说明您如何在这些表中存储事务?我实际上没有学生或教职员工表,因为所有 ID 都来自我们的 VLE,使用 Frog.API.get('users.getInfo') 调用。【参考方案2】:

我会为 Recipient_ID 编制索引,这样您就可以在任何给定点专门搜索 1 个人,或者至少能够更有效地对您的数据进行分组。如果您确实选择按 category_id 分组,那么我也会向 category_id 添加一个单独或组合的索引。

第二个建议是动态分组和聚合您的数据。例如:

SELECT Recipient_ID, Category_ID, SUM(points) FROM transactions GROUP BY Recipient_ID, Category_ID

这两个建议应该会显着提升你的表现,因为你将直接在数据库上计算,而不是在 PHP/JS 方面为学生计算总分。

【讨论】:

【参考方案3】:

您的问题是您的查询:

SELECT * FROM `transactions`

随着您的数据集变得越来越大,这将需要更长的时间来加载并需要更多的内存来存储它。而是确定您具体需要哪些数据。如果是针对特定用户:

SELECT SUM(points) FROM `transactions` WHERE Recipient_ID=[x]

或者,如果您想要所有学生的所有金额:

SELECT Recipient_ID, SUM(points) AS Total_Points FROM `transactions` GROUP BY Recipient_ID;

要加快对特定字段的选择,您可以为该字段添加索引。这将加快选择速度,尤其是随着表格的增长。

ALTER TABLE `transactions` ADD INDEX Recipient_ID (Recipient_ID);

或者如果你想显示transactions中所有条目的分页列表:

SELECT * FROM `transactions` LIMIT [page*num_records_per_page],[num_records_per_page];

e.g.: SELECT * FROM `transactions` LIMIT 0,25 ORDER BY Datetime; # First 25 records

【讨论】:

非常感谢汤姆。这提高了我系统上许多区域的速度。

以上是关于优化数据库结构的主要内容,如果未能解决你的问题,请参考以下文章

Mysql 优化 -- 数据库结构

Mysql 性能优化5重要数据库结构优化

MySQL性能优化方法二:表结构优化

Mysql性能优化----SQL语句优化索引优化数据库结构优化系统配置优化服务器硬件优化

mySQL表结构优化

优化数据库结构