优化数据库结构
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
【讨论】:
非常感谢汤姆。这提高了我系统上许多区域的速度。以上是关于优化数据库结构的主要内容,如果未能解决你的问题,请参考以下文章