NULL値はデータベース検索のパフォーマンスにどのように影響しますか?
-
06-07-2019 - |
質問
当社の製品には汎用検索エンジンがあり、検索パフォーマンスを最適化しようとしています。クエリで使用されるテーブルの多くは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テーブルWHERE列 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エンジンはそれについて何かをする必要があります。
サーバーが実行する最も速いことは、そのページを別のページにオフ移動し、その行のエントリをフォワードポインターに置き換えることです。残念ながら、クエリの実行時に追加のルックアップが必要です。1つは行の自然な位置を見つけるため、もう1つは現在の位置を見つけるためです。
したがって、あなたの質問に対する簡単な答えはイエスです。これらのフィールドをヌル不可にすると、検索のパフォーマンスが向上します。これは、検索するレコードのnullフィールドがnull以外に更新されることがよくある場合に特に当てはまります。
もちろん、大きなデータセットに関連する他のペナルティ(特に、インデックスの深さはわずかですが)があり、概念的にそれらを必要とするフィールドでnullを許可しないというアプリケーションの問題がありますが、それはもう1つです問題:)
列にNULLが含まれていない場合、この列を NOT NULL
として宣言するのが最善です。オプティマイザーはより効率的なパスを取ることができます。
ただし、列にNULLが含まれる場合、選択肢はあまりありません(null以外のデフォルト値は、解決するよりも多くの問題を引き起こす可能性があります)。
Quassnoiが言及したように、OracleではNULLはインデックス付けされません。より正確には、すべてのインデックス付けされた列が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;
テストが必要だと思いますが、他の人の経験を知ることは素晴らしいことです。 ms SQLサーバーでの私の経験では、nullは大きなパフォーマンスの問題(違い)を引き起こす可能性があります。非常に簡単なテストで、テーブルのcreateステートメントの関連フィールドにnullが設定されていない場合は45秒、クエリが設定されていない場合は25分以上でクエリが返されることがわかりました(待機をあきらめ、推定クエリプラン)。
テストデータは100万行x 20列で、Windows 8.1のi5-3320標準HDおよび8GB RAM(2GBを使用するSQL Server)/ 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);
nullを持たないことは、私が特に使用していないメモリ最適化テーブルの要件です。ただし、SQLサーバーは明らかに、この特定のケースではデータにnullを持たず、テーブル作成では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が20MB / sから60MB / sの間でホバリングして完全にIOバインドされました。同じクエリの繰り返しには9分かかりました。
「NOT IN」を行うと、Nullableフィールドはパフォーマンスに大きな影響を与える可能性があります。クエリ。すべてのインデックス付きフィールドが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はパフォーマンスに影響するため、使用するかどうかの問題は、データベース設計のバランスをとる行為の1つです。ビジネスニーズとパフォーマンスのバランスを取る必要があります。
必要な場合はヌルを使用する必要があります。たとえば、テーブルに開始日と終了日がある場合があります。多くの場合、レコードが作成された時点で終了日がわかりません。したがって、データが単に存在しないため、パフォーマンスに影響するかどうかにかかわらず、nullを許可する必要があります。ただし、ビジネスルールにより、データがレコードの作成時に存在する必要がある場合は、許可しないでください。 nulls。これにより、パフォーマンスが向上し、コーディングが少し簡単になり、データの整合性が維持されます。
NULLを許可しないように変更する既存のデータがある場合は、その変更の影響を考慮する必要があります。まず、現在nullであるレコードにどの値を入れる必要があるか知っていますか?第二に、更新する必要がある isnull
または coalesce
を使用しているコードがたくさんありますか(これらはパフォーマンスを低下させるので、それらをチェックする必要がない場合) 、コードを変更する必要があります)?デフォルト値が必要ですか?本当に割り当てることができますか?そうでない場合、フィールドがnullにならないことを考慮していない場合、挿入コードまたは更新コードの一部が破損します。時々、人々は彼らがヌルを取り除くことを可能にするために悪い情報を入力するでしょう。そのため、今度は価格フィールドに小数値と「不明」などを含める必要があるため、小数値データ型を適切に設定できず、計算を行うためにあらゆる種類の長さに移動する必要があります。これにより、多くの場合、作成されたヌルよりも悪いまたは悪いパフォーマンスの問題が発生します。 PLusあなたはすべてのコードを通過する必要があり、nullまたはnullではないフィールドへの参照を使用した場合は、データが許可されないために誰かが入れる可能性のある悪い値に基づいて除外または含めるように書き換える必要がありますnullになります。
クライアントデータから多くのデータインポートを行い、nullを許可する必要があるフィールドがあるファイルを取得するたびに、システムにインポートする前にクリーンアップする必要があるガベージデータを取得します。電子メールはこれらの1つです。多くの場合、データはこの値を知らずに入力され、一般に何らかのタイプの文字列データであるため、ユーザーはここに何でも入力できます。メールをインポートして、「わからない」ことを見つけます。 「わからない」に実際にメールを送信しようとするのは難しい。システムが有効な電子メールアドレスを要求し、@記号の存在などをチェックすると、「I@dont.know"このようなガベージデータは、データのユーザーにとってどのように役立ちますか?
nullのパフォーマンスの問題の一部は、引数なしのクエリを記述した結果です。必要なnullを削除するのではなく、where句を再配置するだけでパフォーマンスが向上する場合があります。
私の経験では、NULLは有効な値であり、通常は「わからない」という意味です。わからない場合は、列のデフォルト値を作成したり、NOT NULL制約を強制しようとしたりすることは本当に無意味です。 NULLはたまたま特定のケースです。
NULLの本当の課題は、取得が少し複雑になることです。たとえば、WHERE column_name IN(NULL、 'value1'、 'value2')とは言えません。
多くの列が見つかった場合、または特定の列に多くのNULLが含まれている場合は、データモデルを再検討する必要があると思います。たぶん、それらのヌル列は子テーブルに入れることができますか?例:名前、自宅の電話番号、携帯電話番号、ファックス番号、勤務先番号、緊急番号などの電話番号が記載されたテーブル。これらのうち1つまたは2つだけを入力でき、正規化する方が適切です。
必要なのは、一歩下がってデータへのアクセス方法を確認することです。これは値を持つべき列ですか?これは特定の場合にのみ値を持つ列ですか?これは多くのクエリを実行する列ですか?