怎么空值的影响表现在数据库搜索?
-
06-07-2019 - |
题
在我们的产品,我们有一个通用搜索引擎,并试图optimze搜索性。很多的表格中使用查询允许空值。我们应该重新设计我们表以禁止空值的最优化或没有?
我们的产品上运行 Oracle
和 MS SQL Server
.
解决方案
在 Oracle
中, NULL
值未编入索引,i。即这个查询:
SELECT *
FROM table
WHERE column IS NULL
将始终使用全表扫描,因为索引不会覆盖您需要的值。
不仅如此,这个查询:
SELECT column
FROM table
ORDER BY
column
也会使用全表扫描并出于同样的原因进行排序。
如果您的值本身不允许 NULL
,则将该列标记为 NOT NULL
。
其他提示
额外的回答是要大声关注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.
如您所见,正在使用索引。
此致 罗布。
简短回答:是的,有条件的!
空值和性能的主要问题与正向查找有关。
如果在表中插入一个具有空值的行,则将其放在它所属的自然页面中。查找该记录的任何查询都会在适当的位置找到它。到目前为止很容易....
...但是让我们说页面填满了,现在这行被包含在其他行中。还是顺利......
...直到更新行,并且null值现在包含某些内容。行的大小已超出可用空间,因此数据库引擎必须对此做些什么。
服务器要做的最快的事情是将关闭该行的页面移动到另一个页面,并用前向指针替换行的条目。不幸的是,这需要在执行查询时进行额外查找:一个用于查找行的自然位置,另一个用于查找其当前位置。
因此,对您的问题的简短回答是肯定的,使这些字段不可为空将有助于搜索性能。如果经常发生您搜索的记录中的空字段更新为非空,则尤其如此。
当然,还有其他处罚(特别是I / O,尽管在很小程度上索引深度)与较大的数据集相关联,然后你在应用程序问题上禁止在概念上需要它们的字段中的空值,但是,嘿,这是另一个问题:)
如果你的专栏不包含空,最好是宣布此列 NOT NULL
, ,优化程序可能能够采取更有效的道路。
但是,如果你有空,在你的专栏你没有太多的选择(非空缺省值可以创造更多问题比它解决的).
作为Quassnoi mentionned,Null未编入Oracle,或者更准确地说,行不会被编入索引的,如果所有索引列空,这意味着:
- 这Null可能加速你的研究,因为该指数将减少行
- 你仍然可以指数的空行,如果添加另一个不NULL列的指数或甚至一个恒定不变。
以下脚本演示了一种指数NULL values:
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;
我想说测试是必需的,但了解其他人的经历真好。根据我在ms sql server上的经验,nulls可以而且确实会导致大量的性能问题(差异)。在一个非常简单的测试中,我看到一个查询在45秒内返回,当在表create语句中的相关字段上设置not null时,在25分钟内没有设置它(我放弃了等待,只是在估计的查询计划)。
测试数据是100万行x20列,由i5-3320普通高清上的62个随机小写字母字符和Windows 8.1上的8GB RAM(SQL Server使用2GB)/ SQL Server 2012企业版构成。使用随机数据/不规则数据使测试成为现实的“更差”是很重要的。案件。在这两种情况下,表都被重新创建并重新加载随机数据,这些数据在已经具有适当可用空间量的数据库文件上花费了大约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分钟。
当执行“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 |
-----------------------------------------------------------------------------
是否因为影响性能而使用Null的问题是数据库设计的平衡行为之一。您必须平衡业务需求与性能。
如果需要,应使用Null。例如,您可能在表格中有一个开始日期和结束日期。您通常不会知道创建记录时的结束日期。因此,您必须允许空值,无论它们是否影响性能,因为数据根本不存在。但是,如果数据必须由业务规则在创建记录时存在,那么您不应该允许空值。这将提高性能,使编码更简单,并确保数据完整性得以保留。
如果您要将现有数据更改为不再允许空值,则必须考虑该更改的影响。首先,您是否知道需要将哪些值放入当前为空的记录中?其次,你有很多使用 isnull
或 coalesce
的代码需要更新(这些会降低性能,所以如果你不再需要检查它们,你应该改变代码)?你需要一个默认值吗?你能真的指定一个吗?如果不是,那么如果不考虑该字段不再为空,则某些插入或更新代码会中断。有时人们会输入不良信息以允许他们摆脱空值。所以现在价格字段需要包含十进制值和“未知”之类的东西,因此不能正确地成为十进制数据类型,然后你必须去各种长度才能进行计算。这通常会导致性能问题与创建的null相比更糟或更差。您需要查看所有代码,并且无论您使用的引用是null还是不为null,您都需要根据可能存在的错误值重写以排除或包含,因为不允许数据为空。
我从客户端数据中进行了大量的数据导入,每次我们得到一个文件,其中一些应该允许空值的字段没有,我们得到的垃圾数据需要在导入我们的系统之前进行清理。电子邮件就是其中之一。通常输入的数据不知道这个值,而且通常是某种类型的字符串数据,因此用户可以在此输入任何内容。我们去导入电子邮件并找到“我不知道”的内容。很难尝试实际发送电子邮件至“我不知道”。如果系统需要有效的电子邮件地址并检查是否存在@符号,我们会得到'I@dont.know"这样的垃圾数据如何对数据用户有用?
null的一些性能问题是编写不可查询的查询的结果。有时只需重新排列where子句而不是消除必要的null就可以提高性能。
根据我的经验,NULL是一个有效值,通常意味着“不知道”。如果您不知道那么为列构成一些默认值或尝试强制执行一些NOT NULL约束实在是没有意义的。 NULL恰好是特定情况。
NULL的真正挑战是它使检索复杂化了一些。例如,你不能说WHERE column_name IN(NULL,'value1','value2')。
就个人而言,如果你发现很多列,或者某些列包含很多NULL,我想你可能想重新访问你的数据模型。也许这些空列可以放入子表中?例如:一个带有电话号码的表格,其中包括姓名,家庭电话,手机,传真号码,工作号码,紧急号码等...您可能只填充其中的一个或两个,这样可以更好地将其正常化。
您需要做的是退后一步,看看如何访问数据。这是一个应该有值的列吗?这是一个只对某些情况有价值的列吗?这是一个会被大量查询的专栏吗?