冗余不是错?
下面的四张表分别表示工作流实例、工作项,股票、股票价格历史记录。大小分别表示表中数据量的多少。
对于上面的两种类型的主从关系表,我觉得可以把其归类为对历史数据的处理。但是其不同之处在于工作流实例的表是会被不断的插入新记录的表,而对于股票相较于工作流实例则很少会插入新的记录。因此在处理这两种类型的历史记录时,只有把股票的当前价格进行冗余才是比较理想的。如果在工作流实例中也把当前的工作项步骤、当前处理时间、当前处理人等信息冗余。则在新建实例或是处理相应的工作项时,都必须修改相应的实例的冗余字段,这使得工作量会加倍。为了保证数据的物理一致性,在修改记录时会对相应的数据页施加闩锁,如果位于同一数据页的实例被同时修改时就会发生阻塞。
冗余的目的只是为了尽快找出当前的状态,如果不使用冗余的话,我们可以使用一个相关子查询来进行判断
SELECT TOP 10 * FROM dbo.WorkInstance p
JOIN dbo.WorkItems i ON p.ProcID=i.ProcID
AND i.TaskID=(SELECT MAX(TaskID) FROM dbo.WF_WORK_ITEMS i1 WHERE i1.ProcID=i.ProcID)
-- TaskID表示某实例对应的工作项
上面的查询中虽然对dbo.WF_WORK_ITEMS进行了两次查询,但是子查询中取最大TaskID的值时,只是在对应的主键(ProcID、TaskID)上的索引级别进行的查询,我们并没有为此创建额外的索引,而某一个实例所对应的工作项也不是很多。所以查询的速度是很快的。请确保有对应的复合索引且顺序为ProcID、TaskID,如果表中没有这个复合索引,则使用这种子查询的方式就会对查询的性能造成一定影响了。
在工作流的表中把部门名称、创建人名称等等一些冗余的字段保存起来,浪费存储空间是其次。我发现有的项目中存在如下的情况,查询某个人或某个部门创建的工作流实例,于是程序中把相应的人员姓名或部门名称传至数据库进行查询,本来是可以在工作流表中对应的人员编号和部门编号上建立个索引就得了,但这时要么你就在存储过程中先根据人员姓名或部门名称查询出相应的ID后,再去工作流表中查找相应的记录。但在过程中定义的变量用于查询会出现"参数嗅探"的问题,唯一的解决办法是创建另一个过程把编号传进去,或是写一个相关子查询,简单问题复杂化,就因为程序本来可以直接传对应的编号来查询。更糟糕的是,有些干脆在部门名称和人员姓名的字段上建立了索引,这些索引又占用一些额外的存储空间,更使得插入数据时的性能比没有这两个索引时低2倍多。我觉得这在一定程序上放纵了开发人员。同时,部门和人员表也是很小的表,在查询中关联上这种小表对查询的影响也不大。所以像这类的冗余还是不要为好。
而对于股票的当前价格如果用上面的方式查询出最大的编号或日期对应的记录,由于某一股票会对应大量的历史记录(假设用户需要保留一年或半年的记录),所以查询起来会比工作项要耗时一些。因此,对于股票的当前价格冗余一下,同时股票的表又不会被经常的新加入记录时,我觉得才是冗余的好时机。
另外,关于表中大部分字段都允许为NULL的问题。表中的记录代表一个实体,如果对应的这些字段都为NULL,说明你的这个表不能很好的表示某个实体的相关属性或是它可能表示了很多实体,这可能暗示你的表中存在冗余字段。因此,应该考虑一下为什么会允许有这么多NULL字段出现在表中。SQLSERVER为了识别字段是否为NULL,在对记录存储时对每一个对应的可为NULL的字段用一位进行标识,在判断是否为NULL时会有额外的开销。同时,这些NULL字段在程序中进行处理时也要额外的判断。因此,除非必要还是不要任由这些本该有意义的字段可为NULL。
因此,对于OLTP来说大部分冗余都是有害无利的。以上个人一点看法,如有不正确的地方请指正!