【问题标题】:How do NULL values affect performance in a database search?NULL 值如何影响数据库搜索的性能?
【发布时间】:2010-11-04 06:32:07
【问题描述】:

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

我们的产品同时在 OracleMS SQL Server 上运行。

【问题讨论】:

  • Jakob,你在使用 NULL 时遇到过什么样的性能问题?
  • 好吧 - 到目前为止没有问题。但我记得我读过一篇关于使用空值时性能降低的文章。因此,我们的团队开始讨论是否应该允许空值 - 我们还没有得出任何结论。我们有一些非常庞大的表,其中包含数百万行和很多客户,所以这对项目来说是一个很大的变化。但是客户提出了关于搜索引擎性能的问题。
  • 如果您在搜索引擎中遇到性能问题,我会在消除空值之前查看许多其他地方。从索引开始,查看执行计划以了解实际发生的情况。看看你的 where 子句,看看它们是否是 sargeable。看看你返回的内容,你是否使用了 select * (如果你有一个连接,对性能不利,因为至少重复一个字段从而浪费网络资源),你是否使用子查询而不是连接?你用过游标吗? where 子句是否足够排他?您是否对第一个字符使用了通配符?不断地不断。

标签: sql database oracle database-performance query-performance


【解决方案1】:

Oracle 中,NULL 的值未编入索引,即。 e.这个查询:

SELECT  *
FROM    table
WHERE   column IS NULL

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

不仅如此,这个查询:

SELECT  column
FROM    table
ORDER BY
        column

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

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

【讨论】:

  • 相同的查询将如何影响 MS SQL SERVER?
  • SQL Server 索引 NULL 的
  • 您可以使用包含文字值的基于函数的索引来解决此限制,例如 CREATE INDEX MY_INDEX ON MY_TABLE (MY_NULLABLE_COLUMN, 0)
  • 嘿伙计们,这并不总是正确的 - 请参阅下面的答案
【解决方案2】:

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

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

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

...但是假设页面已填满,现在该行被挤在其他行中。还是很顺利……

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

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

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

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

【讨论】:

  • 将这些列设置为 NOT NULL 并不能解决“行迁移”问题:如果在插入时不知道该信息,则会输入另一个默认值(如 '.')并且您当真实数据替换默认值时,仍会迁移行。在 Oracle 中,您可以适当地设置 PCTFREE 以防止行迁移。
  • 您能否添加一个基准或文档以凭经验支持这一主张?你引用的问题是当长度x的值增加到x+x时出现的,真的是null还是数据更新问题?
【解决方案3】:

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

声明:

这个查询:

SELECT * FROM table WHERE 列 为空

将始终使用全表扫描

不正确。下面是使用带有字面值的索引的反例:

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.

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

问候, 抢。

【讨论】:

    【解决方案4】:

    我会说测试是必需的,但很高兴了解其他人的经验。根据我在 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 显然会做最快的事情,在这种特定情况下,这似乎很大程度上支持在数据中没有空值并且不使用表创建时为 null。

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

    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 分钟。

    【讨论】:

      【解决方案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;
      

      【讨论】:

        【解决方案6】:

        在执行“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 |
        -----------------------------------------------------------------------------
        

        【讨论】:

          【解决方案7】:

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

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

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

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

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

          【讨论】:

            【解决方案8】:

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

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

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

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

            【讨论】:

            • 我只使用 null 来表示不存在的外键(例如,发票项目表上的“折扣券”外键可能不存在)。但是,我不在非外键列中使用空值;正如你所说,它“通常”意味着不知道。空值的问题在于它们可能意味着几件事——“未知”、“不适用”、“不存在”(我的情况)等。在非关键情况下,您总是必须将名称映射到 NULL当你终于开始使用它时。最好在列本身中将映射值定义为实际值,而不是在任何地方重复映射。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-10-27
            • 2018-06-22
            • 1970-01-01
            相关资源
            最近更新 更多