技术开发 频道

十步优化SQL Server中的数据访问

  第七步:应用反范式化,使用历史表和预计算列

  反范式化

  如果你正在为一个OLTA(在线事务分析)系统设计数据库,主要指为只读查询优化过的数据仓库,你可以(和应该)在你的数据库中应用反范式化和索引,也就是说,某些数据可以跨多个表存储,但报告和数据分析查询在这种数据库上可能会更快。

  但如果你正在为一个OLTP(联机事务处理)系统设计数据库,这样的数据库主要执行数据更新操作(包括插入/更新/删除),我建议你至少实施第一、二、三范式,这样数据冗余可以降到最低,数据存储也可以达到最小化,可管理性也会好一点。

  无论我们在OLTP系统上是否应用范式,在数据库上总有大量的读操作(即select查询),当应用了所有优化技术后,如果发现数据检索操作仍然效率低下,此时,你可能需要考虑应用反范式设计了,但问题是如何应用反范式化,以及为什么应用反范式化会提升性能?让我们来看一个简单的例子,答案就在例子中。

  假设我们有两个表OrderDetails(ID,ProductID,OrderQty) 和 Products(ID,ProductName)分别存储订单详细信息和产品信息,现在要查询某个客户订购的产品名称和它们的数量,查询SQL语句如下:

SELECT Products.ProductName,OrderQty

  
FROM OrderDetails INNER JOIN Products

  
ON OrderDetails.ProductID = Products.ProductID

  
WHERE SalesOrderID = 47057

  如果这两个都是大表,当你应用了所有优化技巧后,查询速度仍然很慢,这时可以考虑以下反范式化设计:

  1)在OrderDetails表上添加一列ProductName,并填充好数据;

  2)重写上面的SQL语句

 SELECT ProductName,OrderQty

  
FROM OrderDetails

  
WHERE SalesOrderID = 47057

  注意在OrderDetails表上应用了反范式化后,不再需要连接Products表,因此在执行SQL时,SQL引擎不会执行两个表的连接操作,查询速度当然会快一些。

  为了提高select操作性能,我们不得不做出一些牺牲,需要在两个地方(OrderDetails 和 Products表)存储相同的数据(ProductName),当我们插入或更新Products 表中的ProductName字段时,不得不同步更新OrderDetails表中的ProductName字段,此外,应用这种反范式化设计时会增加存储资源消耗。

  因此在实施反范式化设计时,我们必须在数据冗余和查询操作性能之间进行权衡,同时在应用反范式化后,我们不得不重构某些插入和更新操作代码。有一个重要的原则需要遵守,那就是只有当你应用了所有其它优化技术都还不能将性能提升到理想情况时才使用反范式化。同时还需注意不能使用太多的反范式化设计,那样会使原本清晰的表结构设计变得越来模糊。

  历史表

  如果你的应用程序中有定期运行的数据检索操作(如报表),如果涉及到大表的检索,可以考虑定期将事务型规范化表中的数据复制到反范式化的单一的历史表中,如利用数据库的Job来完成这个任务,并对这个历史表建立合适的索引,那么周期性执行的数据检索操作可以迁移到这个历史表上,对单个历史表的查询性能肯定比连接多个事务表的查询速度要快得多。

  例如,假设有一个连锁商店的月度报表需要3个小时才能执行完毕,你被派去优化这个报表,目的只有一个:最小化执行时间。那么你除了应用其它优化技巧外,还可以采取以下手段:

  1)使用反范式化结构创建一个历史表,并对销售数据建立合适的索引;

  2)在SQL Server上创建一个定期执行的操作,每隔24小时运行一次,在半夜往历史表中填充数据;

  3)修改报表代码,从历史表获取数据。

  创建定期执行的操作

  按照下面的步骤在SQL Server中创建一个定期执行的操作,定期从事务表中提取数据填充到历史表中。

  1)首先确保SQL Server代理服务处于运行状态;

  2)在SQL Server配置管理器中展开SQL Server代理节点,在“作业”节点上创建一个新作业,在“常规”标签页中,输入作业名称和描述文字;

  3)在“步骤”标签页中,点击“新建”按钮创建一个新的作业步骤,输入名字和TSQL代码,最后保存;

  4)切换到“调度”标签页,点击“新建”按钮创建一个新调度计划;

  5)最后保存调度计划。

  在数据插入和更新中提前执行耗时的计算,简化查询

  大多数情况下,你会看到你的应用程序是一个接一个地执行数据插入或更新操作,一次只涉及到一条记录,但数据检索操作可能同时涉及到多条记录。

  如果你的查询中包括一个复杂的计算操作,毫无疑问这将导致整体的查询性能下降,你可以考虑下面的解决办法:

  1)在表中创建额外的一列,包含计算的值;

  2)为插入和更新事件创建一个触发器,使用相同的计算逻辑计算值,计算完成后更新到新建的列;

  3)使用新创建的列替换查询中的计算逻辑。

  实施完上述步骤后,插入和更新操作可能会更慢一点,因为每次插入和更新时触发器都会执行一下,但数据检索操作会比之前快得多,因为执行查询时,数据库引擎不会执行计算操作了。

  小结

  至此,我们已经应用了索引,重构TSQL,应用高级索引,反范式化,以及历史表加速数据检索速度,但性能优化是一个永无终点的过程,最下一篇文章中我们将会介绍如何诊断数据库性能问题。

2
相关文章