前回の記事では、特定の2種類のインデックスについて学びました。この記事では、インデックスの使用法の原則について学びます。毎日SQLを調整する場合、最初に考えるのは「インデックスの追加」かもしれませんが、これを考慮しましたか。 ?アプローチに問題はありますか、すべてがすべての反対であり、より良いほど、インデックスは同じです
mysqlでは、インデックスもファイルに格納され、インデックスはツリータイプのデータ構造であるため、データ構造を維持する必要があることは誰もが知っています。したがって、テーブルにインデックスが多すぎる場合(通常、テーブルは10インデックスを超えない)の場合、このテーブルのインデックスは多くのスペースを占有し、そのようなテーブルが追加、削除、または変更されると、多くのリソースを消費し、パフォーマンスの問題を引き起こします。
インデックス使用の原則
1.カラム分散
式:
count(distinct(column_name)):count(*)、列とすべてのデータ行のすべての異なる値の比率。
同じ数のデータ行の場合、分子が大きいほど、列の分散が大きくなります。
土語:列の繰り返し値が多い場合、分散は低くなり、繰り返し値が少ないほど、分散は高くなります。
例:名前と性別で別々にインデックスを作成します。性別で
作成されたインデックスを使用してデータを取得する場合、重複する値が多すぎるため、より多くの行をスキャンする必要があります。たとえば、性別列にインデックスを作成してから、実行プランを確認します。
ALTER TABLE user_innodb DROP INDEX idx_user_gender;
ALTER TABLE user_innodb ADD INDEX idx_user_gender (gender); -- 耗时比较久
EXPLAIN SELECT * FROM `user_innodb` WHERE gender = 0;
「張さん」のように名前のばらつきが大きく、1行だけスキャンする必要があります。
ALTER TABLE user_innodb DROP INDEX idx_user_name;
ALTER TABLE user_innodb ADD INDEX idx_user_name (name);
EXPLAIN SELECT * FROM `user_innodb` WHERE name = '张三';
結論: インデックスを作成するには、分散(選択性)の高いフィールドを使用します。
B + Treeに重複する値が多すぎて、MySQLオプティマイザーが、インデックスが作成されていても、インデックスが全表スキャンを使用する
場合とそれほど変わらないことを検出した場合、必ずしもインデックスが作成されるとは限りません。
2.ジョイントインデックスの左端のマッチング原則
ここで2つのポイントを説明する必要があります。
- 単一列インデックスは、特別なジョイントインデックスと見なすことができます
- 共同指数は、指数
ユーザーテーブルに名前と電話の共同インデックスを作成しました。
ALTER TABLE user_innodb DROP INDEX comidx_name_phone;
ALTER TABLE user_innodb ADD INDEX comidx_name_phone (name,phone);
ジョイントインデックスは、B + Treeの複合データ構造であり、左から右の順序で検索ツリーを構築します
(名前は左側、電話は右側)。
この写真から、名前が注文され、電話が乱れていることがわかります。名前が等しい場合、電話が注文されます。
このとき、where name = 'Mic'およびphone = '133xx'を使用してデータをクエリすると、B + Treeは最初に名前を比較して、次の検索の方向(左または右)を決定します。名前が同じ場合は、電話を比較してください。ただし、クエリ条件に名前がない場合は、最初のステップでどのノードをチェックする必要があるかわかりません。名前は検索ツリーを構築するときの最初の比較要素であり、インデックスは使用されないためです。
あなたはあなた自身のデータベースでそれを試すことができます。
ジョイントインデックス(ABC)では、ABC、A、ABが後に続くwhereを使用してインデックスを使用できます。インデックスは、where B、where BC、whereACでは使用できません。
結論:ジョイントインデックスを作成するときは、最も一般的に使用される列を左端に配置してください。最初のフィールドを順番に使用することはできません。中断することはできません。
ジョイントインデックスabがある場合、where条件の後にwhere b = XXおよびa = XXもインデックスを使用できます。なぜですか?基礎となるオプティマイザーは自動的に最適化するため、abジョイントインデックスを使用することを知っておいてください。
グラフィカルな理解:
別の:
CREATE INDEX idx_name on user_innodb(name);
CREATE INDEX idx_name_phone on user_innodb(name,phone);
当我们创建一个联合索引的时候,按照最左匹配原则,用左边的字段 name 去查询
的时候,也能用到索引,所以第一个索引完全没必要。
如果我们创建三个字段的索引 index(a,b,c),相当于创建三个索引:
index(a)
index(a,b)
index(a,b,c)
虽然说相当于三个索引,但是 **注意** :联合索引算一个索引
上記は、MySQLジョイントインデックスの左端の一致原理です。
カバーインデックス
**表に戻る:**非主キーインデックスでは、最初にインデックスから主キーインデックスのキー値を見つけ、次に主キー値からインデックスにないデータを見つけます。1つをスキャンします。主キーインデックスに基づくクエリよりも多くのインデックスツリー。このプロセスはテーブルにコールバックされます。
補助索引において、単一列索引であろうと共同索引であろうと、選択データ列が索引からのみ取得できる場合は、データ領域から読み取る必要はありません。この時点で使用される索引は表を返さないようにするカバーリングインデックスと呼ばれます。
まず、ジョイントインデックスを作成しましょう。
-- 创建联合索引
ALTER TABLE user_innodb DROP INDEX comixd_name_phone;
ALTER TABLE user_innodb ADD INDEX `comixd_name_phone` (`name`,`phone`);
カバーインデックスは、次の3つのクエリステートメントで使用されます。
EXPLAIN SELECT name,phone FROM user_innodb WHERE name= '张三' AND phone = '13888888888';
EXPLAIN SELECT name FROM user_innodb WHERE name= '张三' AND phone = ' 13888888888';
EXPLAIN SELECT phone FROM user_innodb WHERE name= '张三' AND phone = ' 13888888888';
Extraの「Usingindex」という値は、カバーするインデックスが使用されることを意味します。
*を選択すると、カバーインデックスは使用されません。
明らかに、カバーするインデックスはIOの数とデータアクセスの量を減らすので、クエリの
効率を大幅に向上させることができます。
4.インデックス条件(ICP)でプッシュ
インデックス条件プッシュダウン(理解)、5.6以降の完全な機能。セカンダリインデックスにのみ適用されます。ICPの目標は、アクセステーブルの完全な行の読み取り数を減らし、それによってI / O操作を減らすことです。
ここで説明するプッシュダウンは、実際には、サーバーレイヤーでフィルタリングする必要なしに、ストレージエンジンでフィルタリングアクションが実行されることを意味します。
例:
そのようなテーブルがあり、last_nameとfirst_nameにジョイントインデックスを作成します。
drop table employees;
CREATE TABLE `employees`
(
emp_no int(11) NOT NULL,
birth_date date NULL,
first_name varchar(14) NOT NULL,
last_name varchar(16) NOT NULL,
gender enum ('M','F') NOT NULL,
hire_date date NULL,
PRIMARY KEY (emp_no)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
alter table employees add index idx_lastname_firstname(last_name,first_name);
ここで、FattyWangやThinWangなど、姓がwangで、姓がziであるすべての従業員にクエリを実行します。SQLのクエリ:
select * from employees where last_name='wang' and first_name LIKE '%zi' ;
通常、文字は左から右にソートされるため、先頭に%を追加すると、インデックスに基づいて比較することはできません。したがって、インデックスの比較とフィルタリングに使用できるのは、last_name(姓)フィールドのみです。
したがって、クエリプロセスは次のようになります。
- ジョイントインデックス(3つのプライマリキー値:6、7、8)に従って、wangのすべてのセカンダリインデックスデータを検索します。
- テーブルに戻り、主キーインデックスのすべての適格なデータ(3つのデータ)をクエリします。
- これらの3つのデータをサーバー層に返し、サーバー層で名前がziで終わる従業員を除外します。
ことに留意されたいインデックスの比較は、ストレージエンジンで実行され、そしてデータレコードの比較は、サーバレイヤで実行されます。first_nameの条件をインデックスフィルタリングに使用できない場合、サーバーレイヤーはfirst_nameの条件をストレージエンジンに渡さないため、2つの不要なレコードが読み取られます。
このとき、last_name = 'wang'を満たすレコードが100,000ある場合、読み取る必要のないレコードは9,999になります。では、first_nameフィールドに基づくフィルタリングをストレージエンジンレイヤーで実行できますか?
2番目のクエリ方法:
- 共同インデックス(3つの主キー値:6、7、8)に従って王のすべての二次インデックスデータを見つけます
- 次に、first_nameがziで終わるインデックスをセカンダリインデックス(1インデックス)から除外します。
- 次に、テーブルに戻り、主キーインデックスのすべての適格なデータ(1つのデータ)をクエリして、サーバーレイヤーに返します。
明らかに、主キーインデックスのデータをクエリする2番目の方法は少なくなります。
ICPはデフォルトでオンになっています。つまり、セカンダリインデックスでは、条件をストレージエンジンにプッシュダウンできる限り、介入なしでプッシュダウンします。
set optimizer_switch = 'index_condition_pushdown=on';
この時点での実行プラン:インデックス条件を使用します
。first_nameLIKE%zi 'をストレージエンジンにプッシュした後、必要な1レコードのみがデータテーブルから読み取られます。
ICPをオフにします。
set optimizer_switch = 'index_condition_pushdown=off;
パラメータの表示:
show variables like 'optimizer_switch';
次のSQLを実行します。次の場所を使用します。
explain select * from employees where last_name='wang' and first_name LIKE '%zi';
Whereを使用するということは、ストレージエンジンから取得したデータが条件を満たしていないため、サーバーレイヤーでフィルタリングする必要があることを意味します。
最初にlast_name条件を使用してインデックス範囲をスキャンし、データテーブルレコードを読み取り、次に比較してfirst_name LIKE '%zi'条件を満たしているかどうかを確認します。現時点では、3つのうち1つだけが条件を満たす。
インデックスの作成と使用
インデックスはクエリのパフォーマンスを向上させる上で大きな役割を果たしているため、私たちの目標は可能な限りインデックスを使用することです。
インデックスの作成
- 順序の並べ替えと結合を決定する場所に使用される(on)フィールドとgroupbyフィールドにインデックスを作成します
- インデックスが多すぎないようにしてください。(通常は10以下)-スペースの浪費、更新が遅い
- 性別など、分散(識別)が低いフィールドにはインデックスを付けないでください。-分散が低すぎるため、スキャンラインが多すぎます。インデックスを使用できません
- 頻繁に更新される値は、主キーまたはインデックスとして使用しないでください。-ページ分割を引き起こす
- 複合インデックスは、ハッシュ性の高い(識別性の高い)値を最初に置きます。
- 複合インデックスを作成できる場合は、単一列のインデックスを作成しないでください。
- 順序付けられていない値(IDカード、UUIDなど)をインデックスとして使用することはお勧めしません-障害、分割
- フィールドが長すぎる場合は、プレフィックスインデックスを作成してください
CREATE TABLE 'pre_test' ( `content` varchar(20) DEFAULT NULL, KEY `pre_idx` (`content` (6)) )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
インデックスを使用する場合
- インデックス列で関数(replace \ SUBSTR \ CONCAT \ sum count avg)、式、計算(+-* /)を使用します。
explain SELECT * FROM `student` where id+1 = 4;
- 文字列は引用符で囲まれていません。暗黙の変換が発生します
ALTER TABLE user_innodb DROP INDEX comidx_name_phone;
ALTER TABLE user_innodb add INDEX comidx_name_phone (name,phone);
explain SELECT * FROM `user_innodb` where name = 136; -- 没有用到索引
explain SELECT * FROM `user_innodb` where name = '136'; -- 用到了索引
注:フィールドタイプはintであり、where条件が一重引用符 ''で追加されている場合はインデックスを付けることができます。たとえば、id = '123'の場合はインデックスを使用できます
- %が前にある条件のように-すべてのデータが%が前にある条件を満たしているという意味ではありませんか?インデックスは絶対に必要ありません。フィルタリングのコストが高すぎるため、現時点ではフルテキストインデックスを使用できます。
- 否定的なクエリ
- NOT LIKEできません:
explain select *from employees where last_name not like 'wang'
- !=(<>)およびNOT INは、場合によっては次のことができます。
explain select * from employees where emp_no not in (1) explain select * from employees where emp_no <> 1
注:SQLステートメントがインデックスを使用するかどうかは、データベースのバージョン、データ量、およびデータ選択に関連しています。
結局のところ、実際には、インデックスを使用するかどうかは、最終的にオプティマイザーの最終決定になります。
オプティマイザーは何に基づいていますか?
コストオーバーヘッド(Cost Base Optimizer)に基づいており、ルール(Rule-Based Optimizer)に
も、セマンティクスにも基づいていません。コストが何であれ実行します。
インデックスの使用に関する基本原則はありますが、特定のルールはありません。どのような状況でもインデックスを使用する必要があるというルールはなく、どのような状況でもルールは使用されません。