48

在我们的产品中,我们有一个通用的搜索引擎,并试图优化搜索性能。查询中使用的许多表都允许空值。我们是否应该重新设计我们的表以禁止空值进行优化?

我们的产品同时在Oracle和上运行MS SQL Server

4

8 回答 8

42

Oracle,NULL值没有被索引,即这个查询:

SELECT  *
FROM    table
WHERE   column IS NULL

将始终使用全表扫描,因为索引不涵盖您需要的值。

不仅如此,这个查询:

SELECT  column
FROM    table
ORDER BY
        column

出于同样的原因,还将使用全表扫描和排序。

如果您的值本质上不允许NULL',则将该列标记为NOT NULL

于 2009-06-19T10:24:45.550 回答
16

简短的回答:是的,有条件的!

空值和性能的主要问题与前向查找有关。

如果您将一行插入到表中,并且具有空值,它将被放置在它所属的自然页面中。任何查找该记录的查询都会在适当的位置找到它。到目前为止很容易......

...但是假设页面已填满,现在该行被拥抱在其他行之间。还是顺利...

...直到行被更新,并且空值现在包含一些东西。该行的大小已超出其可用空间,因此数据库引擎必须对其进行处理。

服务器做的最快的事情是将该行该页面移到另一个,并用前向指针替换该行的条目。不幸的是,这需要在执行查询时进行额外的查找:查找行的自然位置,查找行的当前位置。

因此,您的问题的简短回答是肯定的,使这些字段不可为空将有助于搜索性能。如果您搜索的记录中的空字段经常更新为非空字段,则尤其如此。

当然,还有其他与较大数据集相关的惩罚(特别是 I/O,尽管索引深度很小),然后您会遇到应用程序问题,即在概念上需要它们的字段中不允许空值,但是,这是另一个问题 :)

于 2009-06-19T10:15:54.117 回答
14

一个额外的答案,以引起对 David Aldridge 对 Quassnoi 接受的答案的评论的额外关注。

该声明:

这个查询:

SELECT * FROM table WHERE column 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.

如您所见,该索引正在被使用。

问候,罗布。

于 2009-06-20T09:07:38.510 回答
7

我会说测试是必需的,但很高兴了解其他人的经验。根据我在 ms sql server 上的经验,空值可以而且确实会导致大量性能问题(差异)。现在在一个非常简单的测试中,我看到查询在 45 秒内返回,当在 table create 语句中的相关字段上设置了 not null 并且超过 25 分钟没有设置时(我放弃了等待,只是在估计的查询计划)。

测试数据为 100 万行 x 20 列,由 i5-3320 普通 HD 和 8GB RAM(SQL Server 使用 2GB)/Windows 8.1 上的 SQL Server 2012 Enterprise Edition 上的 62 个随机小写字母组成。使用随机数据/不规则数据使测试成为现实的“更糟糕”的情况非常重要。在这两种情况下,表都被重新创建并重新加载了随机数据,这些数据在已经有适当可用空间的数据库文件上花费了大约 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 = page set,其他一切都是默认的。没有索引。

alter table myTable rebuild partition = all with (data_compression = page);

没有空值是我没有专门使用的内存优化表的要求,但是 sql server 显然会做最快的事情,在这种特定情况下,这似乎很大程度上支持在数据中没有空值并在表创建。

此表上相同形式的任何后续查询将在两秒内返回,因此我假设标准默认统计信息并且可能使 (1.3GB) 表适合内存运行良好。IE

select count(field19) from myTable where field19 
                       not in (select field18 from myTable) 1000000

另一方面,没有 null 并且不必处理 null 情况也使查询更简单、更短、更不容易出错并且通常更快。如果可能的话,最好至少在 ms sql server 上避免空值,除非它们是明确要求的并且不能合理地从解决方案中解决。

从一个新表开始,并将其大小调整为 10m 行/13GB,相同的查询需要 12 分钟,考虑到硬件和没有使用的索引,这是非常可观的。对于信息查询完全是 IO 绑定的,IO 徘徊在 20MB/s 到 60MB/s 之间。重复相同的查询需要 9 分钟。

于 2014-10-19T00:47:00.837 回答
5

如果您的列不包含 NULL,最好声明此列NOT NULL,优化器可能会采用更有效的路径。

但是,如果您的列中有 NULL,则您没有太多选择(非空默认值可能会产生比它解决的问题更多的问题)。

正如 Quassnoi 提到的,NULL 在 Oracle 中没有索引,或者更准确地说,如果所有索引列都是 NULL,则不会对行进行索引,这意味着:

  • NULL 可能会加速您的研究,因为索引的行数会更少
  • 如果将另一个 NOT 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;
于 2009-06-19T11:00:04.307 回答
4

在执行“NOT IN”查询时,可为空的字段会对性能产生很大影响。因为所有索引字段都设置为 null 的行没有在 B-Tree 索引中编制索引,所以 Oracle 必须执行全表扫描以检查 null 整体,即使存在索引也是如此。

例如:

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 |
-----------------------------------------------------------------------------
于 2009-06-19T21:40:11.013 回答
3

是否因为 Null 影响性能而使用它们的问题是数据库设计的平衡行为之一。您必须平衡业务需求与性能。

如果需要,应使用空值。例如,您可能在表格中有开始日期和结束日期。您通常不会知道创建记录时的结束日期。因此,无论是否影响性能,您都必须允许空值,因为数据根本不存在。但是,如果根据业务规则,数据必须在创建记录时存在,那么您不应该允许空值。这将提高性能,使编码更简单,并确保保持数据完整性。

如果您想要更改现有数据以不再允许空值,那么您必须考虑该更改的影响。首先,您知道需要将什么值放入当前为空的记录中?其次,您是否有很多代码正在使用isnullcoalesce你需要更新哪些(这些东西会降低性能,所以如果你不再需要检查它们,你应该更改代码)?你需要一个默认值吗?你真的可以分配一个吗?如果不考虑该字段不能再为空,则某些插入或更新代码会中断。有时人们会输入不良信息以使他们摆脱空值。所以现在价格字段需要包含十进制值和“未知”之类的东西,因此不能正确地成为十进制数据类型,然后你必须去各种长度才能进行计算。这通常会产生与创建的 null 一样糟糕或更糟的性能问题。另外,您需要检查所有代码,以及您在哪里使用过引用该文件为 null 或不为 null 的文件,

我从客户端数据进行了大量数据导入,每次我们得到一个文件,其中一些应该允许空值的字段不允许,我们得到需要在导入系统之前清理的垃圾数据。电子邮件就是其中之一。通常在不知道该值的情况下输入数据,并且通常是某种类型的字符串数据,因此用户可以在此处键入任何内容。我们去导入电子邮件并找到“我不知道”的东西。很难尝试实际向“我不知道”发送电子邮件。如果系统需要一个有效的电子邮件地址并检查是否存在 @ 符号之类的东西,我们会得到“I@dont.know”这样的垃圾数据对数据的用户有什么用处?

一些与 null 相关的性能问题是由编写 nonsargable 查询造成的。有时只是重新排列 where 子句而不是消除必要的 null 可以提高性能。

于 2009-06-19T17:15:55.853 回答
0

根据我的经验,NULL 是一个有效值,通常意味着“不知道”。如果您不知道,那么为该列添加一些默认值或尝试强制执行一些 NOT NULL 约束确实是没有意义的。NULL 恰好是一种特殊情况。

NULL 的真正挑战是它使检索变得有点复杂。例如,您不能说 WHERE column_name IN (NULL,'value1','value2')。

就个人而言,如果您发现很多列,或者某些列包含很多 NULL,我认为您可能需要重新访问您的数据模型。也许那些空列可以放入子表中?例如:一个带有电话号码的表格,其中包括姓名、家庭电话、手机、传真号码、工作号码、紧急号码等……您可能只填充其中的一两个,这样会更好地对其进行规范化。

您需要做的是退后一步,看看如何访问数据。这是一个应该有值的列吗?这是仅在某些情况下具有值的列吗?这是一个会被大量查询的列吗?

于 2009-06-19T11:21:46.463 回答