数据库设计进行优化

时间:2014-01-31 10:33:43

标签: php mysql database-design

几年前,我为11-16岁的学生设计了一个奖励系统,包括PHP,JavaScript和MySQL。

前提很简单:

  • 员工向不同类别的学生发放积分(“积极态度和行为”,“模范公民”等)
  • 学生积累这些积分,然后将它们花在我们的网上商店(iTunes优惠券等)

现有系统

数据库结构也很简单(可能太多了):

交易

239,189行

CREATE TABLE `transactions` (
 `Transaction_ID` int(9) NOT NULL auto_increment,
 `Datetime` date NOT NULL,
 `Giver_ID` int(9) NOT NULL,
 `Recipient_ID` int(9) NOT NULL,
 `Points` int(4) NOT NULL,
 `Category_ID` int(3) NOT NULL,
 `Reason` text NOT NULL,
 PRIMARY KEY  (`Transaction_ID`),
 KEY `Giver_ID` (`Giver_ID`),
 KEY `Datetime` (`Datetime`),
 KEY `DatetimeAndGiverID` (`Datetime`,`Giver_ID`),
 KEY `Recipient_ID` (`Recipient_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=249069 DEFAULT CHARSET=latin1

分类

34行

CREATE TABLE `categories` (
 `Category_ID` int(9) NOT NULL,
 `Title` varchar(255) NOT NULL,
 `Description` text NOT NULL,
 `Default_Points` int(3) NOT NULL,
 `Groups` varchar(125) NOT NULL,
 `Display_Start` datetime default NULL,
 `Display_End` datetime default NULL,
 PRIMARY KEY  (`Category_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

奖励

82行

CREATE TABLE `rewards` (
 `Reward_ID` int(9) NOT NULL auto_increment,
 `Title` varchar(255) NOT NULL,
 `Description` text NOT NULL,
 `Image_URL` varchar(255) NOT NULL,
 `Date_Inactive` datetime NOT NULL,
 `Stock_Count` int(3) NOT NULL,
 `Cost_to_User` float NOT NULL,
 `Cost_to_System` float NOT NULL,
 PRIMARY KEY  (`Reward_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=91 DEFAULT CHARSET=latin1

购买

5,889行

CREATE TABLE `purchases` (
 `Purchase_ID` int(9) NOT NULL auto_increment,
 `Datetime` datetime NOT NULL,
 `Reward_ID` int(9) NOT NULL,
 `Quantity` int(4) NOT NULL,
 `Student_ID` int(9) NOT NULL,
 `Student_Name` varchar(255) NOT NULL,
 `Date_DealtWith` datetime default NULL,
 `Date_Collected` datetime default NULL,
 PRIMARY KEY  (`Purchase_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=6133 DEFAULT CHARSET=latin1

问题

系统运行良好一段时间。它现在开始大量减慢某些查询。

基本上,每次我需要访问学生的奖励积分总数时,所需的查询需要年龄。以下是一些示例查询及其运行时间:

全校排名前15位的学生,不包括出勤类别

SELECT CONCAT( s.Firstname,  " ", s.Surname ) AS  `Student` , s.Year_Group AS `Year Group`, SUM( t.Points ) AS  `Points` 
FROM frog_rewards.transactions t
LEFT JOIN frog_shared.student s ON t.Recipient_ID = s.id
WHERE t.Datetime >  '2013-09-01' AND t.Category_ID NOT IN ( 12, 13, 14, 26 )
GROUP BY t.Recipient_ID
ORDER BY  `Points` DESC 
LIMIT 0 , 15

EXPLAIN 1

  • 运行时间: 44.8425秒

SELECT Recipient_ID, SUM(points) AS Total_Points FROM {交易{1}}

EXPLAIN 2

  • 运行时间: 9.8698秒

现在我明白了,特别是对于第二个查询,我不应该运行一个会返回如此大量行的调用,但是系统运行的框架的局限性意味着我别无选择如果我想向教师/导师/年度经理/领导者展示学生的总奖励积分,以便查看和分析。

解决方案的时间

幸运的是,我们被迫使用的框架正在发生变化。我们现在将使用oAuth而不是可怕的,过时的JavaScript小部件格式。

不幸的是 - 或者,我想,幸运的是 - 这意味着我们必须重写相当多的系统。

重写系统时我打算查看的主要方面之一是数据库结构。随着时间的推移,它只会变大,所以我需要做一些面向未来的工作。

因此,我的主要问题是:存储学生总分的最有效和最有效的方法是什么?

我能想到的唯一想法就是有一个名为GROUP BY Recipient_ID的单独表格,其中包含totalsStudent_ID字段。每当一名工作人员给出一些要点时,它会在Points表中添加一行,但也会更新transactions表。

效率这么高吗?拥有totals类型字段会有效吗?我将如何更新/保持最佳状态?

除了主要问题之外,如果有人对数据库表的优化有一般改进的建议,请告诉我。

提前致谢, 邓肯

1 个答案:

答案 0 :(得分:3)

您的设计没有什么特别的错误,应该让它像您报告的那样慢。我认为必须有其他因素起作用,例如服务器在运行时过载或慢速运行。只有你能够发现是否是这种情况。

为了测试您的设计,我在台式计算机上运行的2008 SQL Server上重新创建了它。我有一个标准的计算机,单个硬盘,而不是SSD,没有raid等。所以在适当的数据库服务器上,结果应该更好。当你使用MySQL时,我不得不对设计进行一些更改,但是没有任何更改会影响性能,只是这样我才能在我的数据库上运行它。

这是我使用的表结构,我不得不猜测你在StudentStaff表中会有什么,因为你没有说明这些。我也冒昧地更改了TransactionGiver_ID Receiver_ID表格中的字段名称,因为我假设只有员工给予积分,学生才能收到积分。

database tables

我生成了随机数据来填充表格,其数量与您在数据库中所说的行数相同

data generation

我跑了两个你说已经花了很长时间的查询,我已经改变它们以适合我的设计,但我(希望)结果是相同的

SELECT TOP 15
        Firstname + ' ' + Surname
       ,Year_Group
       ,SUM(Points) AS Points
FROM    points.[Transaction]
        INNER JOIN points.Student ON points.[Transaction].Student_ID = points.Student.Student_ID
WHERE   [Datetime] > '2013-09-01'
        AND Category_ID NOT IN ( 12, 13, 14, 26 )
GROUP BY Firstname + ' ' + Surname
       ,Year_Group
ORDER BY SUM(Points) DESC 


SELECT  Student_ID
       ,SUM(Points) AS Total_Points
FROM    points.[Transaction]
GROUP BY Student_ID

返回的两个查询结果大约为1秒。除了主键上默认生成的CLUSTERED索引之外,我没有在表上创建任何其他索引。查看执行计划,查询处理器估计实现以下索引可以提高查询成本 81.0309%

CREATE NONCLUSTERED INDEX [<Name of Missing Index>]
ON [points].[Transaction] ([Datetime],[Category_ID])
INCLUDE ([Student_ID],[Points])

execution plan

正如其他人所评论的那样,在花费大量时间重新设计数据库之前,我会先寻找瓶颈。

<强>更新

我意识到我从未真正解决过您的具体问题:

  

什么是最有效和最有效的学生存储方式   总分?

     

我能想到的唯一想法是有一个单独的表叫做   带有Student_ID和Points字段的总计。每次都是员工   给出了一些观点,它在事务表中添加了一行但是   还会更新总计表。

除非您已经探索了其他可能的方法来加速数据库,否则我不建议保留单独的总分。一个单独的计数器可能与事务不同步,然后你必须调和所有内容并追踪出错的地方,以及正确的总数应该是什么。

在尝试提高速度之前,您应始终专注于保持数据的正确性和一致性。大多数情况下,正确(标准化)的数据模型运行得足够快。

在我工作的一个地方,我们发现加速数据库的最具成本效益的方法就是升级硬件;比花费许多工时重新设计数据库要快得多,便宜得多:)