在我们的产品中,我们有一个通用的搜索引擎,并试图优化搜索性能。查询中使用的许多表都允许空值。我们应该重新设计我们的表以禁止空值进行优化吗?
我们的产品同时在Oracle
和MS SQL Server
上运行。
答案 0 :(得分:26)
在Oracle
中,NULL
值未编入索引,i。即这个查询:
SELECT *
FROM table
WHERE column IS NULL
将始终使用全表扫描,因为索引不会覆盖您需要的值。
不仅如此,这个查询:
SELECT column
FROM table
ORDER BY
column
也会使用全表扫描并出于同样的原因进行排序。
如果您的值本身不允许NULL
,则将该列标记为NOT NULL
。
答案 1 :(得分:13)
额外的回答是要大声关注David Aldridge对Quassnoi接受的答案的评论。
声明:
此查询:
SELECT * FROM表WHERE列 IS NULL
将始终使用全表扫描
不是真的。以下是使用带有文字值的索引的计数器示例:
SQL> create table mytable (mycolumn)
2 as
3 select nullif(level,10000)
4 from dual
5 connect by level <= 10000
6 /
Table created.
SQL> create index i1 on mytable(mycolumn,1)
2 /
Index created.
SQL> exec dbms_stats.gather_table_stats(user,'mytable',cascade=>true)
PL/SQL procedure successfully completed.
SQL> set serveroutput off
SQL> select /*+ gather_plan_statistics */ *
2 from mytable
3 where mycolumn is null
4 /
MYCOLUMN
----------
1 row selected.
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'))
2 /
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
SQL_ID daxdqjwaww1gr, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ * from mytable where mycolumn
is null
Plan hash value: 1816312439
-----------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 2 |
|* 1 | INDEX RANGE SCAN| I1 | 1 | 1 | 1 |00:00:00.01 | 2 |
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("MYCOLUMN" IS NULL)
19 rows selected.
如您所见,正在使用索引。
此致 罗布。
答案 2 :(得分:8)
简短回答:是的,有条件的!
空值和性能的主要问题与正向查找有关。
如果在表中插入一个具有空值的行,则将其放在它所属的自然页面中。查找该记录的任何查询都会在适当的位置找到它。到目前为止很容易....
...但是让我们说页面填满了,现在这行被包含在其他行中。还是顺利......
...直到更新行,并且null值现在包含某些内容。行的大小已超出可用空间,因此数据库引擎必须对此做些什么。
服务器要做的最快的事情是将关闭该行的页面移动到另一个页面,并用前向指针替换行的条目。不幸的是,这需要在执行查询时进行额外查找:一个用于查找行的自然位置,另一个用于查找其当前位置。
因此,对您的问题的简短回答是肯定的,使这些字段不可为空将有助于搜索性能。如果经常发生您搜索的记录中的空字段更新为非空,则尤其如此。
当然,还有其他处罚(特别是I / O,尽管在很小程度上索引深度)与较大的数据集相关联,然后你在应用程序问题上禁止在概念上需要它们的字段中的空值,但是,嘿,这是另一个问题:)
答案 3 :(得分:5)
如果您的列不包含NULL,最好声明此列NOT NULL
,优化器可能会采用更有效的路径。
但是,如果列中有NULL,则没有太多选择(非空默认值可能会产生比解决的问题更多的问题)。
正如Quassnoi所提到的,在Oracle中没有对NULL进行索引,或者更确切地说,如果所有索引列都为NULL,则不会对行编制索引,这意味着:
以下脚本演示了一种索引NULL值的方法:
CREATE TABLE TEST AS
SELECT CASE
WHEN MOD(ROWNUM, 100) != 0 THEN
object_id
ELSE
NULL
END object_id
FROM all_objects;
CREATE INDEX idx_null ON test(object_id, 1);
SET AUTOTRACE ON EXPLAIN
SELECT COUNT(*) FROM TEST WHERE object_id IS NULL;
答案 4 :(得分:5)
我想说测试是必需的,但了解其他人的经历真好。根据我在ms sql server上的经验,nulls可以而且确实会导致大量的性能问题(差异)。在一个非常简单的测试中,我已经看到一个查询在45秒内返回,当在表创建语句中的相关字段上设置not null时,超过25分钟,它没有设置(我放弃了等待,只是拿了一个在估计的查询计划中达到峰值。)
测试数据是100万行x20列,由i5-3320普通高清上的62个随机小写字母字符和Windows 8.1上的8GB RAM(SQL Server使用2GB)/ SQL Server 2012企业版构成。使用随机数据/不规则数据使测试更加真实,更糟糕的是,#34;案件。在这两种情况下,表都被重新创建并重新加载随机数据,这些数据在已经具有适当可用空间量的数据库文件上花费了大约30秒。
select count(field0) from myTable where field0
not in (select field1 from myTable) 1000000
CREATE TABLE [dbo].[myTable]([Field0] [nvarchar](64) , ...
vs
CREATE TABLE [dbo].[myTable]([Field0] [nvarchar](64) not null,
出于性能原因,两者都有表选项data_compression =页面集,其他所有内容都是默认的。没有索引。
alter table myTable rebuild partition = all with (data_compression = page);
没有空值是内存优化表的一个要求,我并没有特别使用它,但是sql server显然会做最快的事情,在这个特定的情况下看起来很大程度上支持不在数据中使用空值而不使用在表上创建null。
此表上相同表单的任何后续查询都会在两秒内返回,因此我假设标准默认统计信息并且可能使(1.3GB)表适合内存运行良好。 即。
select count(field19) from myTable where field19
not in (select field18 from myTable) 1000000
除了没有空值而不必处理空案例之外,还使查询更简单,更短,更不容易出错并且通常更快。如果可能的话,最好通常在ms sql server上避免空值,除非它们是明确要求的,并且不能合理地解决问题。
从新表开始并将其调整为10米行/ 13GB相同的查询需要12分钟,考虑到硬件并且没有使用索引,这是非常值得尊敬的。对于信息查询完全IO绑定IO悬停在20MB / s到60MB / s之间。重复相同的查询花了9分钟。
答案 5 :(得分:3)
是否因为影响性能而使用Null的问题是数据库设计的平衡行为之一。您必须平衡业务需求与性能。
如果需要,应使用Null。例如,您可能在表格中有一个开始日期和结束日期。您通常不会知道创建记录时的结束日期。因此,您必须允许空值,无论它们是否影响性能,因为数据根本不存在。但是,如果数据必须由业务规则在创建记录时存在,那么您不应该允许空值。这将提高性能,使编码更简单,并确保数据完整性得以保留。
如果您要将现有数据更改为不再允许空值,则必须考虑该更改的影响。首先,您是否知道需要将哪些值放入当前为空的记录中?其次,您是否有大量使用isnull
或coalesce
需要更新的代码(这些会降低性能,因此如果您不再需要检查它们,则应更改代码)?你需要一个默认值吗?你能真的指定一个吗?如果不是,那么如果不考虑该字段不再为空,则某些插入或更新代码会中断。有时人们会输入不良信息以允许他们摆脱空值。所以现在价格字段需要包含十进制值和“未知”之类的东西,因此不能正确地成为十进制数据类型,然后你必须去各种长度才能进行计算。这通常会导致性能问题与创建的null相比更糟或更差。您需要查看所有代码,并且无论您使用的引用是空的还是非空,您都需要根据可能存在的错误值重写以排除或包含,因为不允许数据为空。
我从客户端数据中进行了大量的数据导入,每次我们得到一个文件,其中一些应该允许空值的字段没有,我们得到的垃圾数据需要在导入我们的系统之前进行清理。电子邮件就是其中之一。通常输入的数据不知道这个值,而且通常是某种类型的字符串数据,因此用户可以在此输入任何内容。我们去导入电子邮件并找到“我不知道”的内容。很难尝试实际发送电子邮件到“我不知道”。如果系统需要一个有效的电子邮件地址并检查是否存在@符号,我们会得到'I@dont.know'这样的垃圾数据如何对数据用户有用?
null的一些性能问题是编写不可查询的查询的结果。有时只需重新排列where子句而不是消除必要的null就可以提高性能。
答案 6 :(得分:3)
在执行“NOT IN”查询时,可空字段会对性能产生很大影响。由于所有索引字段设置为null的行未在B树索引中编制索引,因此即使存在索引,Oracle也必须执行全表扫描以检查是否为空。
例如:
create table t1 as select rownum rn from all_objects;
create table t2 as select rownum rn from all_objects;
create unique index t1_idx on t1(rn);
create unique index t2_idx on t2(rn);
delete from t2 where rn = 3;
explain plan for
select *
from t1
where rn not in ( select rn
from t2 );
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50173 | 636K| 3162 (1)| 00:00:38 |
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL| T1 | 50205 | 637K| 24 (5)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| T2 | 45404 | 576K| 2 (0)| 00:00:01 |
---------------------------------------------------------------------------
查询必须检查空值,因此必须对t1中的每一行执行t2的全表扫描。
现在,如果我们使字段不可为空,它可以使用索引。
alter table t1 modify rn not null;
alter table t2 modify rn not null;
explain plan for
select *
from t1
where rn not in ( select rn
from t2 );
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2412 | 62712 | 24 (9)| 00:00:01 |
| 1 | NESTED LOOPS ANTI | | 2412 | 62712 | 24 (9)| 00:00:01 |
| 2 | INDEX FULL SCAN | T1_IDX | 50205 | 637K| 21 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN| T2_IDX | 45498 | 577K| 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------
答案 7 :(得分:0)
根据我的经验,NULL是一个有效值,通常意味着“不知道”。如果您不知道那么为列构成一些默认值或尝试强制执行一些NOT NULL约束实在是没有意义的。 NULL恰好是特定情况。
NULL的真正挑战是它使检索复杂化了一些。例如,你不能说WHERE column_name IN(NULL,'value1','value2')。
就个人而言,如果你发现很多列,或者某些列包含很多NULL,我想你可能想重新访问你的数据模型。也许这些空列可以放入子表中?例如:一个带有电话号码的表格,其中包括姓名,家庭电话,手机,传真号码,工作号码,紧急号码等...您可能只填充其中的一个或两个,这样可以更好地将其正常化。
您需要做的是退后一步,看看如何访问数据。这是一个应该有值的列吗?这是一个只对某些情况有价值的列吗?这是一个会被大量查询的专栏吗?