2009-06-19 47 views
20

在我們的產品中,我們有一個通用搜索引擎,並試圖優化搜索性能。查詢中使用的很多表都允許使用空值。我們是否應該重新設計我們的表以禁止優化或不優化空值?NULL值如何影響數據庫搜索中的性能?

我們的產品上都OracleMS SQL Server運行。

+0

Jakob,你遇到過什麼樣的性能問題? – 2009-06-19 10:14:18

+0

好 - 到目前爲止沒有問題。但我記得我讀過一篇關於使用空值時性能較慢的文章。因此,我們的團隊開始了討論,無論我們是否應該允許空值 - 我們還沒有陷入任何混淆。我們有一些非常巨大的表格,其中有數百萬行和很多客戶,所以這對項目來說是一個很大的改變。但客戶提出了關於搜索引擎性能的問題。 – 2009-06-19 10:26:13

+2

如果你在搜索引擎中的性能有問題,我會在消除空值之前查看許多其他地方。從索引開始,查看執行計劃以查看實際發生的情況。看看你的條款,看看他們是否是可悲的。看看你正在返回的是什麼,你是否使用select *(如果你有一個連接,至少會重複一次,因此會影響nework資源,所以你使用select *),你使用子查詢而不是連接嗎?你用光標了嗎? where子句是否足夠獨佔?你是否爲第一個字符使用了通配符?等等。 – HLGEM 2009-06-19 17:21:40

回答

19

OracleNULL值未編入索引,我。即此查詢:

SELECT * 
FROM table 
WHERE column IS NULL 

將始終使用全表掃描,因爲索引不包含您需要的值。

更重要的是,這個查詢:

SELECT column 
FROM table 
ORDER BY 
     column 

也將使用全表掃描和排序一樣的道理。

如果你的價值觀本質上不容許NULL的,然後標記列NOT NULL

6

簡答:是的,有條件!

空值和性能的主要問題是使用正向查找。

如果你插入一行到表中,空值,它被放置在於其屬於自然頁面。任何查詢該記錄的查詢都會在適當的位置找到它。易至今....

...但是我們要說的頁面填滿了,現在該行被擁抱在其他行之間。仍然順利...

...直到行更新,並且空值現在包含某些內容。行的大小超出了可用空間的大小,因此數據庫引擎必須對此做些事情。

服務器要做的最快的事情是將該頁的關閉頁移動到另一頁,並用一個前向指針替換該行的條目。不幸的是,當執行查詢時,這需要額外的查找:一個查找行的自然位置,另一個查找當前位置。

因此,您的問題的簡短答案是肯定的,這些字段不可爲空將有助於搜索性能。如果經常發生在您搜索的記錄中的空字段更新爲非空,這尤其如此。

當然,還有其他懲罰(特別是I/O,儘管指向深度很小)與更大的數據集關聯,然後您在應用程序問題上禁止在概念上需要它們的字段中的空值,但是,嘿,這就是另一個問題:)

+2

將這些列設置爲NOT NULL不會解決「行遷移」問題:如果在插入時未知信息,將輸入另一個默認值(如'。'),並且在真實時仍然會遷移行數據將取代默認值。在Oracle中,您可以適當地設置PCTFREE以防止行遷移。 – 2009-06-19 10:25:34

4

如果列不包含空值,最好聲明此列NOT NULL,優化程序可能能夠採取更有效的路徑。

但是,如果你有你的列的NULL你沒有太多的選擇(比它解決的一個非空的默認值可能會產生更多的問題)。

作爲Quassnoi mentionned,空值未在甲骨文索引,或更精確地說,如果所有的索引列是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; 
0

以我的經驗NULL是一個有效的值,通常是指「不知道」。如果你不知道,那麼爲列設置一些默認值或嘗試強制執行一些NOT NULL約束確實毫無意義。 NULL恰好是一個特定的情況。

對NULL的真正挑戰是它使檢索複雜一點。例如,你不能說WHERE column_name IN(NULL,'value1','value2')。

個人而言,如果您發現很多列或者某些列包含大量空值,我想您可能需要重新訪問您的數據模型。也許這些空列可以放入子表中?例如:一個電話號碼名稱,家庭電話,手機,傳真號碼,工作號碼,緊急號碼等等的表格......您只能填入其中的一個或兩個,並且會更好地對其進行標準化。

你需要做的是退一步,看看數據將如何被訪問。這是一個應該有價值的專欄嗎?這是隻對某些情況有價值的專欄嗎?這是一個將被質疑很多的專欄嗎?

1

是否使用空值是因爲它們影響性能,這是數據庫設計平衡行爲之一。你必須平衡業務需求與性能。

如果需要的話,應該使用空值。例如,你可能在表格中有開始日期和結束日期。您通常不知道創建記錄時的結束日期。因此,您必須允許空值,不管它們是否影響性能或不影響性能,因爲數據根本就不存在。但是,如果數據必須按業務規則在創建記錄時存在,那麼您不應該允許空值。這會提高性能,使編碼更簡單一些,並確保數據完整性得以保留。

如果您有現有數據要更改爲不再允許空值,則必須考慮該更改的影響。首先,你知道你需要把什麼值放入當前爲空的記錄中嗎?其次,你是否有很多使用isnull或coalesce的代碼需要更新(這些東西性能下降,所以如果你不再需要檢查它們,你應該改變代碼)?你需要一個默認值嗎?你真的可以指派一個嗎?如果不是,那麼一些插入或更新代碼會中斷,如果它不考慮該字段不能再爲空。有時候人們會輸入不好的信息來讓他們擺脫空位。所以現在價格字段需要包含十進制值和'未知'之類的東西,因此不能正確地成爲十進制數據類型,然後你必須去各種長度才能進行計算。這通常會造成性能問題,比創建的空值差或更差。 PLus你需要瀏覽你的所有代碼,並且你曾經使用過一個引用爲空或不爲空的字段,你需要重寫以排除或包含基於可能的壞值,因爲數據不被允許爲空。

我做了很多從客戶端數據導入的數據,每次我們得到一個文件,其中一些應該允許空值的字段沒有,我們在導入到我們的系統之前需要清理垃圾數據。電子郵件就是其中之一。通常數據輸入不知道這個值,它通常是某種類型的字符串數據,所以用戶可以在這裏輸入任何內容。我們去導入電子郵件並找到「我不知道」的東西。努力嘗試實際發送電子郵件到「我不知道」。如果系統需要有效的電子郵件地址並檢查是否存在@符號,我們會得到'[email protected]'這樣的垃圾數據如何對數據用戶有用?

某些空值的性能問題是編寫非嚴格查詢的結果,有時只是重新排列where子句而不是省略必要的null可以提高性能。

3

在執行「NOT IN」查詢時,可空字段可能會對性能產生重大影響。由於所有索引字段設置爲null的行不會在B-Tree索引中編入索引,因此即使索引存在,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 | 
--------------------------------------------------------------------------- 

查詢必須檢查空值,因此必須做T2的全表掃描對於t1的每一行。

現在,如果我們使字段不可爲空,它可以使用索引。

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 | 
----------------------------------------------------------------------------- 
12

一個額外的答案得出一些額外的關注大衛·阿爾德里奇的評論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. 

正如你所看到的,正在使用的索引。

Regards, Rob。

2

我會說測試是必需的,但很高興知道其他人的經驗。根據我在ms sql server上的經驗,空值可能會導致巨大的性能問題(差異)。在一個非常簡單的測試中,我已經看到在45秒內返回的查詢在表create語句中的相關字段上設置爲非空值,並且在25分鐘內沒有設置時返回(我放棄了等待並剛剛在估計的查詢計劃)。

測試數據是100萬行x 20列,它們是在Windows 8.1上的i5-3320普通HD和8GB RAM(使用2GB的SQL Server)/ SQL Server 2012企業版上的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 =頁面集,其他所有內容都是默認的。沒有索引。

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

由於沒有空是在內存優化表對此我沒有具體然而使用SQL Server將明顯做什麼要求,在這種特定的情況下,似乎是大量有利於沒有在零點最快數據和使用非空表創建。

此表上相同表單的任何後續查詢都會在兩秒內返回,因此我將假定標準默認統計信息以及可能使(1.3GB)表適合內存的操作正常。 即

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

上擱置沒有空值和不必處理null情況下也使得查詢多simplier,更短,減少錯誤,非常正常速度更快。如果可能的話,最好儘量避免在ms sql服務器上出現空值,除非它們是明確要求的,並且不能合理地用於解決方案。

從一個新表開始,將其大小調整到10m行/ 13GB,同樣的查詢需要12分鐘,考慮到硬件和沒有使用的索引,這是非常可敬的。 IO信息查詢完全與IO在20MB/s到60MB/s之間徘徊在一起。重複相同的查詢需要9分鐘。

相關問題