mysql IS NULL インデックスケースの説明を使用する

mysql IS NULL インデックスケースの説明を使用する

導入

MySQL の SQL クエリ ステートメントで is null、is not null、!= を使用すると、インデックスには影響しません。また、where 条件で is null、is not null、!= を使用したためにインデックスが無効化されたり、テーブル全体がスキャンされたりすることはありません。

MySQL の公式ドキュメントでも、null はインデックスの使用に影響を与えないことが明記されています。

MySQL は、col_name = constant_value に使用できるものと同じ最適化を col_name IS NULL に対して実行できます。たとえば、MySQL はインデックスと範囲を使用して、IS NULL で NULL を検索できます。

実際、インデックスが失敗し、テーブル全体がスキャンされる理由は、通常、1 つのクエリで返されるテーブルが多すぎるためです。 MySQL では、インデックスを使用する時間コストはテーブル全体のスキャンよりも高いと計算されるため、MySQL はインデックスを使用するよりもテーブル全体をスキャンすることを優先します。

場合

テーブル `user_info` を作成します (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(11) デフォルト NULL,
  `age` int(4) デフォルト NULL,
  主キー (`id`)、
  キー `index_name` (`name`) BTREE の使用
)ENGINE=InnoDB デフォルト文字セット=utf8mb4;
`user_info` (`id`, `name`, `age`) に VALUES ('1', 'tom', '18') を挿入します。
`user_info` (`id`, `name`, `age`) に VALUES ('2', null, '19') を挿入します。
`user_info` (`id`, `name`, `age`) VALUES ('3', 'cat', '20') に INSERT INTO します。

SQL クエリを実行するときに、is null と is not null を使用しましたが、インデックス クエリが引き続き使用され、インデックス無効化の問題は発生しませんでした。

ここに画像の説明を挿入

ここに画像の説明を挿入

分析する

上記の現象を分析するには、MySQL インデックスの動作原理とインデックス データ構造を詳細に理解する必要があります。次に、ツール解析とバイナリ ファイルの直接表示という 2 つの方法を使用して、MySQL インデックス データ構造を分析します。

ツール分析

innodb_ruby は非常に強力な MySQL 分析ツールであり、MySQL の .ibd ファイルを簡単に解析し、MySQL のデータ構造をより深く理解するために使用できます。

まず、innodb_ruby ツールをインストールします。

rubygems をインストールします。
gem をインストール innodb_ruby

innodb_ruby には多くの機能があります。ここでは MySQL のインデックス構造を解析するためにのみ使用する必要があるため、次のコマンドのみが必要です。その他の機能とコマンドについては、Wiki を参照してください。

innodb_space -s ibdata1 -T sakila/film -I PRIMARY インデックス再帰

主キーインデックスの解析:

$ innodb_space -s /usr/soft/mysql-5.6.31/data -T test/user_info -I PRIMARY インデックス再帰
ルートノード #3: 3 レコード、89 バイト
  記録: (id=1) → (name="tom", age=18)
  記録: (id=2) → (name=:NULL, age=19)
  記録: (id=3) → (name="cat", age=20)

共通インデックス index_name を解析します。

$ innodb_space -s /usr/soft/mysql-5.6.31/data -T test/user_info -I index_name インデックス再帰
ルートノード #4: 3 レコード、38 バイト
  レコード: (name=:NULL) → (id=2)
  レコード: (name="cat") → (id=3)
  記録: (name="tom") → (id=1)

mysqlのインデックス構造を分析すると、インデックスツリーにnull値も格納されており、null値は最小値に処理されてindex_nameインデックスツリーの左側に配置されていることがわかります。

バイナリファイル

user_info テーブルに対応する物理ファイル user_info.ibd を見つけ、UltraEdit などのソフトウェアで開き、5 番目のデータ ページを直接見つけます (MySQL のデフォルトのデータ ページは 16 KB です)。

ここに画像の説明を挿入

図に示すように、これらのバイナリ データは index_name インデックスに対応するインデックス ページ データです。次のように、インデックス レコードのみが選択され、展開されます。

最小レコード 0x00010063

01 B2 01 00 02 00 29 レコードヘッダー情報 69 6E 66 69 6D 75 6D 最小レコード(固定値下限)

最大レコード 0x00010070

00 04 00 0B 00 00 レコードヘッダー情報 73 75 70 72 65 6D 75 6D 最大レコード(固定値上限)

ID 1 インデックス 0x0001007f

03 00 00 00 10 FF F1 レコードヘッダー情報 74 6F 6D フィールド名の値: tom
80 00 00 01 RowID: 主キーIDの値は1です

ID 2 インデックス 0x0001008c

01 00 00 18 00 0B レコードヘッダー情報 フィールド名の値: null
80 00 00 02 RowID: 主キーIDの値は2です

ID 3 インデックス 0x00010097

03 00 00 00 20 FF E8 レコードヘッダー情報 63 61 74 フィールド名値: cat
80 00 00 03 RowID: 主キーIDの値は3です

最小レコードのレコード ヘッダー情報の最後の 2 バイトは、00 29 -> 0x00010063 オフセット 0x0029 -> 0x0001008C であり、これは ID 2 のインデックス位置です。

ID 2 のレコード ヘッダー情報の最後の 2 バイトは 00 0B -> 0x0001008C オフセット 0x000B -> 0x00010097 であり、これは ID 3 のインデックス位置です。

ID 3 のレコード ヘッダー情報の最後の 2 バイトは、FF E8 -> 0x00010097 オフセット 0xFFE8 -> 0x0001007F であり、これは ID 1 のインデックス位置です。

ID 1 のレコード ヘッダー情報の最後の 2 バイトは FF F1 -> 0x0001007F、オフセット 0xFFF1 -> 0x00010070、最大レコードのレコード位置です。

インデックス レコードは一方向のリンク リストを介して直列に接続され、インデックス値によってソートされ、null 値は最小値に処理されてインデックス リンク リストの先頭、つまりインデックス ツリーの左端の位置に配置されていることがわかります。結果は、innodb_ruby ツールによって解析された結果と一致しています。

誤解の理由

なぜ人々は、is null、is not null、!= などの判定条件によってインデックスが失敗し、テーブル全体がスキャンされると誤解するのでしょうか?

インデックスが失敗し、テーブル全体がスキャンされる理由は、通常、1 つのクエリで返されるテーブルが多すぎるためです。 MySQL では、インデックスを使用する時間コストはテーブル全体のスキャンよりも高いと計算されるため、MySQL はインデックスを使用するよりもテーブル全体をスキャンすることを優先します。インデックスを使用する時間コストは、完全なテーブルスキャンの臨界値よりも高くなります。これは、簡単に言えば約 20% です。

詳細な分析プロセスについては、著者の別のブログ投稿「MySQL テーブルの戻りによりインデックス障害が発生する」を参照してください。

つまり、クエリ ステートメントによって返されるテーブルの範囲が全レコードの 20% を超えると、インデックスは無効になります。返されるテーブルの範囲が大きいシナリオでは、is null、is not null、!= などの判定条件がよく出現し、これらの判定条件によってインデックスが無効になると誤解されることがあります。

繰り返し発生するインデックス障害

インデックスの失敗を再現するには、すべてのレコードの 20% を超えるテーブル範囲を返すだけで済みます。次のように、null 以外のレコードを 1000 個挿入します。

区切り文字 //
プロシージャ init_user_info() を作成する 
始める 
	indexNo INT を宣言します。
	インデックス番号を 0 に設定します。
	インデックス番号が1000未満の場合
		トランザクションを開始します。 
			user_info(name,age) に値を挿入します (concat(floor(rand()*1000000000)),floor(rand()*100));
			SET インデックス番号 = インデックス番号 + 1;
		専念; 
	終了しながら;
終わり //
区切り文字 ;
init_user_info() を呼び出します。

現時点では、user_info テーブルには合計 1003 件のレコードがあり、そのうち name 値が null になっているレコードは 1 件だけです。すると、is null 判定ステートメントによって返されるレコードのうち、臨界値を超えないのは 1/1003 のみで、is not null 判定ステートメントによって返されるレコードのうち、1002/1003 は臨界値を大幅に超過し、インデックス エラーが発生します。

次の 2 つの図からわかるように、is null の場合は通常どおりインデックスを使用しますが、is not null の場合は、予想どおり、テーブル戻り率が高いため、インデックスを使用するのではなく、完全なテーブル スキャンを優先します。

ここに画像の説明を挿入

ここに画像の説明を挿入

MySQL のオプティマイザー トレース (MySQL 5.6 でサポート) を使用して、SQL 実行プランを分析します。

optimizer_trace を「有効 = オン」に設定します。
select * from user_info where name is not null; を説明します。
INFORMATION_SCHEMA.OPTIMIZER_TRACE から * を選択します。

オプティマイザー トレースによって出力された実行プランによると、このクエリの場合、フル テーブル スキャンを使用した場合の時間コストは 206.9 ですが、インデックスを使用した場合の時間コストは 1203.4 であり、フル テーブル スキャンよりもはるかに高いことがわかります。したがって、MySQL は最終的にテーブル全体をスキャンすることを選択し、インデックスが失敗します。

{
    "行推定": [
        {
            "テーブル": "`user_info`",
            "範囲分析": {
                "テーブルスキャン": {
                    "rows": 1004, // フルテーブルスキャンには 1004 レコードのスキャンが必要です "cost": 206.9 // フルテーブルスキャンには 206.9 のコストが必要です
                },
                「潜在的範囲指標」: [
                    {
                        "インデックス": "プライマリ",
                        「使用可能」: false、
                        「原因」: 「該当なし」
                    },
                    {
                        "インデックス": "インデックス名",
                        「使用可能」:true、
                        "キーパーツ": [
                            "名前"、
                            「ID」
                        ]
                    }
                ]、
                "設定範囲条件": [],
                "グループインデックス範囲": {
                    「選択」:偽、
                    「原因」: 「グループ化または区別されない」
                },
                「範囲の代替案を分析する」: {
                    "範囲スキャンの代替": [
                        {
                            "インデックス": "インデックス名",
                            「範囲」: [
                                「NULL < 名前」
                            ]、
                            "index_dives_for_eq_ranges": true、
                            "rowid_ordered": 偽、
                            "using_mrr": 偽、
                            "index_only": 偽、
                            "rows": 1002, // インデックスは 1002 レコードをスキャンする必要があります "cost": 1203.4, // インデックスのコストは 1203.4 です
                            「選択」:偽、
                            「原因」:「コスト」
                        }
                    ]、
                    「行順序の交差を分析する」: {
                        「使用可能」: false、
                        「原因」: 「行順序スキャンが少なすぎる」
                    }
                }
            }
        }
    ]
}

mysql IS NULL インデックスケースの使用に関するこの記事はこれで終わりです。mysql IS NULL の使用に関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • MySQL プロセス制御 IF()、IFNULL()、NULLIF()、ISNULL() 関数
  • この記事ではMySQLのNULLについて説明します。
  • MySQL の NULL 値に関する体験談と分析チュートリアルシリーズ
  • MySql の null 関数の使用の共有
  • MySQLでディスクにNULL値を保存する

<<:  CSS で放射状グラデーションを使用してカード効果を実現する

>>:  nginxのインストールと設定の詳細なプロセス記録

推薦する

Angular Cookie の読み取りおよび書き込み操作コード

Angular Cookie の読み取りおよび書き込み操作のコードは次のようになります。 var a...

Vue + 要素を使用して背景データをオプションに動的に表示する

必要:ハードコードされたデータの代わりに、セレクター内のオプション値の動的な表示を実装します。私のロ...

JavaScript インタビュー: 配列の平坦化メソッドを実装する方法

目次1 配列のフラット化とは何ですか? 2 JS標準ライブラリの配列フラット化メソッド3 フラットメ...

VueのSSRサーバーサイドレンダリング例の詳細な説明

サーバーサイドレンダリング (SSR) を使用する理由検索エンジンのクローラーが完全にレンダリングさ...

CSS における重要なカスケード概念の詳細な説明

最近、プロジェクトの過程で問題に遭遇しました。メニューバーを常に上部に表示し、後続の要素をその下に表...

シンプルな計算機を実装する JavaScript コード

この記事では、参考までに、簡単な計算機を実装するためのJavaScriptの具体的なコードを紹介しま...

ファイルのアップロードの進行状況を示す React の例

目次React アップロードファイル表示の進行状況デモフロントエンドにReactアプリケーションを素...

CSS と JavaScript を使用して管理ダッシュボードのレイアウトを構築するためのサンプル コード

あなたが作成するものこの新しいチュートリアルでは、CSS と JavaScript を使用して、レス...

JavaScript キャンバスは影付きのグラフィックとテキストを実装します

キャンバスを使用して、参照用の影付きのグラフィックとテキストを作成します。具体的な内容は次のとおりで...

URL 内の特殊記号の意味を知っていますか?

1.# # は Web ページ内の場所を表します。右側の文字はその位置の識別子です。たとえば、ht...

CSS3 フィルターの違いと応用の詳しい説明:ドロップシャドウフィルターとボックスシャドウ

標準 CSS3 を使用して要素の影の効果を実現するには、2 つの手順があります。1 つ目は一般的なb...

JS 日付コントロール My97DatePicker の基本的な使い方

My97DatePicker は非常に柔軟で使いやすい日付コントロールです。使い方はとても簡単です。...

ページの下部にHTMLフッターを配置する簡単な方法

要件:ページ コンテンツが短く、ブラウザーの高さをサポートできない場合でも、フッターをウィンドウの下...

MySQLクエリのパフォーマンスを分析する方法

目次スロークエリの基礎: データ取得の最適化データベースから不要なデータが要求されていないか確認する...

ウェブサイトデザインの基礎知識:初心者の方はぜひお読みください

今では多くの人がウェブサイト作成に参加していますが、ウェブサイトはどのように作成すればよいのでしょう...