关于数据库的逻辑设计是一个很广泛的问题本文主要针对开发应用中遇到在MS SQL Server上进行表设计时对表的主键设计应注意的问题以及相应的解决办法
主键设计现状和问题
关于数据库表的主键设计一般而言是根据业务需求情况以业务逻辑为基础形成主键
比如销售时要记录销售情况一般需要两个表一个是销售单的概要描述记录诸如销售单号总金额一类的情况另外一个表记录每种商品的数量和金额对于第一个表(主表)通常我们以单据号为主键;对于商品销售的明细表(从表)我们就需要将主表的单据号也放入到商品的明细表中使其关联起来形成主从关系同时该单据号与商品的编码一起形成明细表的联合主键这只是一般情况我们稍微将这个问题延伸一下假如在明细中我们每种商品又可能以不同的价格方式销售有部分按折扣价格销售有部分按正常价格销售要记录这些情况那么我们就需要第三个表而这第三个表的主键就需要第一个表的单据号以及第二个表的商品号再加上自身需要的信息一起构成联合主键;又或者其他情况在第一个主表中本身就是以联合方式构成联合主键那么也需要在从表中将主表的多个字段添加进来联合在一起形成自己的主键
数据冗余存储随着这种主从关系的延伸数据库中需要重复存储的数据将变得越来越庞大或者当主表本身就是联合主键时就必须在从表中将所有的字段重新存储一次
SQL复杂度增加当存在多个字段的联合主键时我们需要将主表的多个字段与子表的多个字段关联以获取满足某些条件的所有详细情况记录
程序复杂度增加可能需要传递多个参数
效率降低数据库系统需要判断更多的条件SQL语句长度增加同时联合主键自动生成联合索引
WEB分页困难由于是联合主键方式(对于多数的子表)那么在WEB页面上要进行分页处理时在自关联时难于处理
解决方案
从上面我们已经看到现有结构存在着相当多的弊端主要是导致程序复杂效率降低并且不利于分页
为解决上述问题本文提出当应用系统后台数据库表间存在主从关系时数据库表额外增加一非业务字段作为主键该字段为数值型;或者当该表需要在应用中进行分页查询时也应考虑如此设计一般地我们也可以几乎为任何表增加一个与业务逻辑无关的字段作为该表的主键字段
由于该字段要作为表的主键那么其首要条件是要保证在该表中要具有唯一性同时结合SQL Server数据库自身的特性可以为其建立一个自增列
create TABLE T_PK_DEMO
(
U_ID BIGINTNOT NULL IDENTITY()
–唯一标识记录的ID
COL_OTHERVARchar() NOT NULL
–其他列
CONSTRAINT PK_T_PK_DEMO PRIMARY KEYNONCLUSTERED
(U_ID)–定义为主键
)
但是SQL Server中的自增列却存在一个比较尴尬的事实那就是该字段一旦定义和使用用户无法直接干预该字段的值完全由数据库系统自身控制
完全数据库系统控制用户无法修改值
在数据库的发布和订阅时使用自增列会比较麻烦
恢复部分数据时使用自增列会比较麻烦
该列的值必须在插入数据后才能获取
鑒于此建议不以自增列的方式来定义而是参考Oracle数据库系统中序列在SQL Server系统中实现类似Oracle数据库系统序列功能这个具体在下面的小节中介绍我们只需要按照普通字段的定义方式修改表定义为
create TABLE T_PK_DEMO
(
U_ID BIGINT NOT NULL
–唯一标识记录的ID
COL_OTHERVARchar(
) NOT NULL
–其他列
CONSTRAINT PK_T_PK_DEMO PRIMARY KEYNONCLUSTERED (U_ID)–定义为主键
)
参照Oracle序列的功能我们需要在SQL Server数据库中创建一个新表以管理序列值
create TABLE T_DB_SEQ
(
SEQ_NAMEVARchar(
) NOT NULL
–序列名称
SEQ_OWNER VARchar(
) NOT NULL DEFAULT ’DBO’
–序列所有者(SYSTEM_USER)
SEQ_CURRENT BIGINTNOT NULL DEFAULT
–序列当前值
SEQ_MIN BIGINTNOT NULL DEFAULT
–序列最小值
SEQ_MAX BIGINTNOT NULL DEFAULT
–序列最小值
SEQ_MAX BIGINTNOT NULL DEFAULT
–序列最大值
SEQ_STEPINT NOT NULL DEFAULT
–序列增长步长
IF_CYCLEINT NOT NULL DEFAULT
–是否循环(
不循环;
循环)
CONSTRAINT T_DB_SEQ PRIMARY KEYCLUSTERED
(SEQ_NAME
SEQ_OWNER)–主键
)
应用系统为需要创建自增列的表创建一个序列名称在表“T_DB_SEQ”中反映为数据库中的一行
第一需要为需要建立序列的表创建一个序列采用方法F_create_SEQ(序列名)该函数传入序列的名称在表“T_DB_SEQ”插入一行序列的所有者采用系统变量SYSTEM_USER
第二获取下一个值采用方法F_GET_NEXT_SEQ_VAL(序列名)该函数根据序列名获取该序列的下一个值根据当前值与增长步长得到同时该函数保证在同时获取同一个序列时应保证并发一致性
第三将返回值返回到应用使用
此外为保证应用的完整性可能还需要提供一些方法的重载方法同时提供一些其他方法
获取序列当前值F_GET_SEQ_CUR_VAL(序列名)
设置序列值F_SET_SEQ_VAL(序列名)
删除序列F_DEL_SEQ(序列名)
判断序列是否存在F_SEQ_exists(序列名)
在主从关系的表设计中子表也使用序列字段作为唯一主键将父表的序列字段作为外键关联
create TABLE T_PK_DEMO_C
(
U_ID BIGINT NOT NULL
–唯一标识记录的ID
COL_OTHERVARchar(
) NOT NULL
–其他列
P_ID INT NOT NULL
–父表ID
CONSTRAINT PK_T_PK_DEMO_C PRIMARY KEY
NONCLUSTERED (U_ID)–定义为主键
CONSTRAINT FK_T_PK_DEMO_C FOREIGN KEY (P_ID)
REFERENCES T_PK_DEMO(U_ID) ON delete CASCADE
)
使用序列的问题及解决办法
由于系统使用一个额外增加一个字段作为主键因此没有为业务逻辑建立主键约束比如在企业用户信息表中要求企业中用户登录名必须唯一一般在创建表时以登录名作为主键这个时候在数据库层自然的创建另一个主键唯一性约束而现在没有使用登录名作为主键那么就没有这个约束解决办法
一是在数据库层解决可以为该表创建一个唯一(UNIQUE)约束或者唯一索引如
alter TABLE T_PK_DEMO ADD CONSTRAINT C_T_PK_DEMO UNIQUE NONCLUSTERED(COL_OTHER)唯一约束
createUNIQUEINDEX IX_T_PK_DEMO ON T_PK_DEMO(COL_OTHER) – 唯一索引
二是在应用端解决也就是在应用中判断该列是否有重复值然后根据判断结果来保证唯一性
我们注意到在之前的例子中主键采用了NONCLUSTERED(非聚蔟)的索引方式关于如何设计索引不是本文的重点在这里仅提供一个建立索引时采用聚蔟方式还是非聚蔟方式的一个一般原则
作为非业务字段的主键列是一个没有重复值的基本不进行更新操作的列并且在SQL Server数据库中聚蔟索引在一个表中只能有一个因此聚蔟索引非常重要需要留给更重要的字段来使用因此对照上表和根据聚蔟索引的重要程度在此处采用非聚蔟方式创建其索引
具体应用
采用这种主键设计方式有诸多好处这已经在前文说明现在就以一个具体的应用来说明如何使用这个主键
当前的应用系统基本上都已经采用B/S方式尽管现在的网络速度已经有大幅度的提高但是由于在WEB应用上用户数量众多同时基本上所有的运算都集中在WEB应用服务器上所以在WEB设计上更要考虑到性能的优化以减少网络流量和对服务器的压力最常见的一个应用就是列表方式展现时的分页方式一般的在数据量小的情况下一般不会怎么注意这个问题通常采用将数据完全取出然后在WEB服务器上进行分页但是当数据量庞大时这种方式就会导致速度降低甚至根本不可用所以一般采用存储过程在数据库端进行分页