长久以来,不同的数据库设计人员在设计表字段时,有的设计字段允许为NULL,有的设置了默认值(不允许字段中出现NULL值)。对于字段允不允许为空的争论,由来已久,但个人的设计原则是不允许出现NULL。下面从各方面来加以讨论和描述:
1. 处理性能:SQLSERVER在NULL的处理上比空字符串处理复杂,它需要在每一行用额外的空间来标记某一字段是NULL,查找数据的时候会检查这个标志,然后根据这个标志返回相应的NULL给用户,所以处理上性能有所损耗,当然在现在的配置机器,应该可以忽略这些时间。根据个人的测试,先创建一个表,表结构如下:
create table testtable
(
intfield int not null
primary key(intfield), -- 从1开始递增
chrfield uniqueidentifier, -- GUID值
vchfield varchar(40), -- 上面字段的字符串形式
dtefield datetime, -- 日期时间
in2field int -- 对应intfield 的值
)
然后插入500万条数据,在插入时,所有列都有值,用如下语句进行查询:
declare @date datetime
set @date = getdate()
select * from testtable
where in2field >= 4999990
select getdate() - @date
然后设置部分行的in2field字段为NULL,再用上面的查询语句查询,对比二种情况下的所用时间,在没有NULL的情况下占用时间较小。
2. 索引问题:如果用了NULL,索引就会无效,变成全表检索,影响处理速度。同时,如果一个单个的列中有不止一行包含 NULL,则无法在该列上创建唯一索引(当然,用默认值的情况下,如果列中有多个默认值,也不能创建唯一索引)。
3. 编码复杂度:对有空值出现的列处理和判断时,需要增加额外的代码量,不管是在SQL中还是在各种前台语言中,复杂度远远超过检查空字符串及一些数字(比如0)来的慢,使用不慎还会引起错误。如:在有空值参与计算的表达式中,无论表达式如何计算,表达式返回的永远是NULL,特别是对于不太熟悉的开发人员,往往会写出如下代码:
select (intfield + in2field) as sumfield from testable where intfield = @intfield
上面表中,intfield 列有空值出现的情况。有的初学者,甚至会写出如下的语句:
select * from testable where intfield = NULL
有的数据库有配置项来决定NULL参与计算时的规则,但个人认为,不要依赖于数据库的特殊功能来帮助处理业务逻辑。
4. 对象映射问题:在对象模型中,一个固定的对象有N个固定的属性,一般情况下,根据所有的属性的组合,我们可以寻找到指定的对象,如果对象的属性发生变化,并不影响该对象本身,但如果属性有了增加或减少,就会改变对象的类。在数据库中的关系映射到Object也是一样,空值属性在某些情况会被认同为该属性并不存在(虽然它会突然又有了,但这应该作为该对象的状态而不是对象出现),这十分不利于关系与Object的映射。在java中,有O/R可以对象和关系进行转换,.net中虽然目前没有相应的并很成熟的工具,但我觉得,采取OO的思想仍是必要的,毕竟,对事物的描述是一个复杂的过程,尤其是在开发大型的数据库上,最现实的问题就是,业务逻辑的规则,将严重地影响编程效率。
5. 集合函数计数问题:大多数集合函数都能在计算时消除空值;COUNT函数则属于例外。对包含空值的一个列使用COUNT函数,空值会从计算中消除。但假如COUNT函数使用一个星号,它就计算所有行,而不管是否存在空值。
6. 空间占用量:NULL不会占用空间,但如果用默认值,所占空间也不会多很多。
7. 规范性:统一所有字段为“不允许空”并设置“缺省值”。这是一种“规范”。空值的问题,应该由数据库本身设计时就解决掉,而不应该在应用程序的数据访问层中进行处理。