私たちの製品には、一般的な検索エンジンがあり、検索パフォーマンスを最適化しようとしています。クエリで使用されるテーブルの多くはnull値を許可します。最適化のためにnull値を許可しないようにテーブルを再設計する必要がありますか?
私たちの製品はOracle
とMS SQL Server
の両方で動作します。
Oracle
では、NULL
の値はインデックス付けされません。 e。このクエリ:
SELECT *
FROM table
WHERE column IS NULL
インデックスは必要な値をカバーしないため、常にフルテーブルスキャンを使用します。
それ以上に、このクエリ:
SELECT column
FROM table
ORDER BY
column
同じ理由で全表スキャンとソートも使用します。
値が本質的にNULL
を許可しない場合は、列にNOT NULL
のマークを付けます。
Quassnoiの承認された回答に関するDavid Aldridgeのコメントにさらに注意を引くための追加の回答。
ステートメント:
このクエリ:
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.
ご覧のとおり、インデックスが使用されています。
よろしく、ロブ。
短い答え:はい、条件付きで!
Null値とパフォーマンスの主な問題は、前方参照です。
Null値を含む行をテーブルに挿入すると、その行は、それが属しているナチュラルページに配置されます。そのレコードを探すクエリは、適切な場所でそれを見つけます。今のところ簡単...
...しかし、ページがいっぱいになり、その行が他の行の間に収まっているとしましょう。まだ順調です...
...行が更新され、null値に何かが含まれるまで。行のサイズが使用可能なスペースを超えて増加したため、DBエンジンはそれに対して何らかの処理を行う必要があります。
サーバーが行う最速のことは、行offを別のページに移動し、行のエントリを前方ポインタに置き換えることです。残念ながら、これにはクエリの実行時に追加のルックアップが必要です。1つは行の自然な場所を見つけるため、もう1つは現在の場所を見つけるためです。
したがって、質問に対する簡単な答えは「はい」です。これらのフィールドをnullにできないようにすると、検索パフォーマンスが向上します。これは、検索するレコードのnullフィールドがnull以外に更新されることがよくある場合に特に当てはまります。
もちろん、より大きなデータセットに関連する他のペナルティ(特に、インデックスの深さはわずかですがI/O)があり、概念的にそれらを必要とするフィールドでnullを許可しないというアプリケーションの問題がありますが、それは別の問題です:)
テストが必要だと思いますが、他の人の経験を知っておくのはいいことです。 ms sqlサーバーでの私の経験では、nullは大きなパフォーマンスの問題(違い)を引き起こす可能性があります。非常に簡単なテストで、テーブル作成ステートメントの関連フィールドにnull以外が設定されている場合に45秒でクエリが返され、設定されていない場合は25分を超えました(待機をあきらめて、ただピークに達しました)推定クエリプラン)。
テストデータは、100万行x 20列で、i5-3320の通常のHDで62個のランダムな小文字のアルファ文字から構成され、8GB RAM(2GBを使用するSQL Server)/ SQL Server 2012 Enterprise Edition Windows 8.1。ランダムデータ/不規則なデータを使用して、テストを現実的な「最悪の」ケースにすることが重要です。どちらの場合も、テーブルは再作成され、すでに適切な量の空きがあったデータベースファイルで約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);
Nullがないことはメモリ内で最適化されたテーブルの要件であり、私は特に使用していませんが、SQLサーバーは明らかに最速の処理を実行します。この特定のケースでは、データにnullがなく、テーブル作成。
このテーブルに対する同じ形式の後続のクエリは2秒で返るので、標準のデフォルトの統計情報を想定し、(1.3GB)テーブルをメモリに収めることがうまく機能していると思います。つまり.
select count(field19) from myTable where field19
not in (select field18 from myTable) 1000000
余談ですが、nullがなく、nullのケースを処理する必要がないため、クエリもはるかに単純で、短く、エラーが発生しにくく、通常は高速です。可能であれば、少なくとも明示的に要求されており、ソリューションから合理的に機能できない場合を除き、ms SQLサーバーでは一般的にnullを回避するのが最善です。
新しいテーブルから始めて、これを最大10m行/ 13GBの同じクエリにサイジングすると、ハードウェアと使用中のインデックスがないことを考えると、12分かかります。情報クエリは完全にIOバインドIO 20MB/sから60MB/sの間でホバリングします。同じクエリを繰り返すと9分かかりました。
列にNULLが含まれていない場合は、この列を宣言することをお勧めしますNOT NULL
、オプティマイザはより効率的なパスをとることができる場合があります。
ただし、列に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;
パフォーマンスに影響を与えるためにNullを使用するかどうかの問題は、データベース設計のバランスをとる行為の1つです。ビジネスニーズとパフォーマンスのバランスをとる必要があります。
必要な場合は、ヌルを使用する必要があります。たとえば、テーブルに開始日と終了日があるとします。多くの場合、レコードが作成されたときの終了日はわかりません。したがって、データが単にそこに置かれるわけではないので、パフォーマンスに影響するかどうかに関係なく、nullを許可する必要があります。ただし、ビジネスルールによって、レコードの作成時にデータが存在している必要がある場合は、許可しないでください。ヌル。これにより、パフォーマンスが向上し、コーディングが少し簡単になり、データの整合性が維持されます。
Nullを許可しないように変更したい既存のデータがある場合は、その変更の影響を考慮する必要があります。まず、現在nullであるレコードにどの値を入れる必要があるか知っていますか?次に、更新する必要があるisnull
またはcoalesce
を使用しているコードがたくさんありますか(これらはパフォーマンスを低下させるため、これらをチェックする必要がなくなった場合は、コード)?デフォルト値が必要ですか?本当に割り当てられますか?そうでない場合、フィールドがnullにならないことを考慮していない場合、挿入または更新コードの一部が壊れます。時々人々はヌルを取り除くことができるように悪い情報を入れます。したがって、価格フィールドには10進数の値や「不明」などを含める必要があるため、適切に10進数のデータ型にすることはできず、計算を行うためにあらゆる種類の長さに移動する必要があります。これにより、多くの場合、作成されるヌルよりも悪いまたは悪いパフォーマンス問題が発生します。 PLusすべてのコードを確認する必要があり、フィールドがnullまたはnullでないことへの参照を使用した場合は、データが許可されていないために誰かが入れる可能性のある悪い値に基づいて除外または含めるように書き換える必要がありますnullであること。
クライアントデータから多くのデータインポートを実行し、nullを許可する必要があるフィールドで許可されていないファイルを取得するたびに、システムにインポートする前にクリーンアップする必要のあるガベージデータを取得します。メールはその1つです。多くの場合、データはこの値を認識せずに入力されます。これは通常、あるタイプの文字列データであるため、ユーザーはここに何でも入力できます。メールをインポートして、「わからない」ことを探します。 「わからない」に実際にメールを送ろうとするのは大変です。システムが有効なメールアドレスを要求し、@記号の存在などを確認すると、「[email protected]」が返されます。このようなガベージデータは、データのユーザーにとってどのように役立ちますか?
Nullに関するパフォーマンスの問題の一部は、引数をとらないクエリを作成したことが原因です。必要なnullを削除するのではなく、where句を再配置するだけでパフォーマンスが向上する場合があります。
NULL可能フィールドは、「NOT IN」クエリを実行するときにパフォーマンスに大きな影響を与える可能性があります。すべてのインデックス付きフィールドがnullに設定されている行はBツリーインデックスでインデックス付けされないため、インデックスが存在する場合でも、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 |
---------------------------------------------------------------------------
クエリはnull値をチェックする必要があるため、t1の各行に対してt2のフルテーブルスキャンを実行する必要があります。
これで、フィールドをnullにできないようにすると、インデックスを使用できるようになります。
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は有効な値であり、通常は「わからない」という意味です。わからない場合は、列のデフォルト値を作成したり、NOT NULL制約を適用したりしても意味がありません。 NULLはたまたま特定のケースです。
NULLの実際の課題は、検索が少し複雑になることです。たとえば、WHERE column_name IN(NULL、 'value1'、 'value2')と言うことはできません。
個人的には、多くの列を見つけた場合、または特定の列に多くのNULLが含まれている場合は、データモデルを再検討する必要があると思います。多分それらのnull列は子テーブルに入れることができますか?例:名前、自宅電話番号、携帯電話番号、faxno、worknumber、emergencynumberなどの電話番号を含むテーブル。
あなたがする必要があるのは、一歩下がって、データがどのようにアクセスされるかを確認することです。これは値が必要な列ですか?これは特定の場合にのみ値を持つ列ですか?これは、頻繁に照会される列ですか?