(1)MySqlインデックス最適化データ構造とパフォーマンス最適化

インデックスは、Mysqlがデータを効率的に取得するのに役立つソートされたデータ構造です

インデックスとは何ですか?


 1. 官方介绍索引是帮助Mysql高效获取数据的数据结构,更通俗的说,数据库索引好比是一本书前面的目录能加快数据库的查询速度
 2. 一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往是存储在磁盘上的文件中的(可能存储在单独的索引文件中,也可能和数据一起存储在数据文件中)
 3. 我们通常所说的索引,包括聚集索引,覆盖索引,组合索引,前缀索引,唯一索引等没有特别说明,默认都是使用B+树结构组织(多路搜索树,并不一定是二叉的)索引
 
 索引的优势和劣势
 优势:
 
 1. 可以提高数据检索的效率,降低数据库的IO成本类似于书的目录
 2. 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
 	被索引的列会自动进行排序,包括【单列索引】和【组合索引】只是组合索引的排序要复杂一些
 	如果按照索引列的顺序进行排序,对应order by 语句来说,效率就会提高很多
 劣势:
 1. 索引会占用磁盘空间
 2.索引虽然会提高查询效率,但是会降低更新表的效率。比如每次对表进行增删改操作Mysql不仅要保存数据,还有保存
 或者更新对应的索引文件

1.データ構造とインデックスの実装

- 二叉树(右边的元素大于去父元素,左边的元素小于其父元素)
- 红黑树  (是一种自平衡二叉查找树)
- Hash表 (Hash表再等值查询时,效率很高,时间复杂度为O(1) 但是不支持范围快速查找,范围查找时还是只能通过扫描全表来实现)
- B-Tree (叶节点具有相同的深度,叶节点的指针为空 所有索引元素不重复 节点中的数据索引从左到右递增排列)
- B+Trees (多叉平衡树  非椰子节点不存储Data,只存储索引(冗余),可以放更多的索引,叶子节点包含所有索引字段 叶子节点用指针连接,提高区间访问的性能)

SHOW GLOBAL STATUS LIKE'Innodb_page_size ';
ツリーの高さが3のB + Treeは、約21,902,400のインデックスを格納できます。

  • 1.クラスター化インデックス-[クラスター化インデックス]:インデックスとデータがまとめられます(リーフノードには完全なデータが含まれます)
  • 2.非クラスター化インデックス-[スパースインデックス]:インデックスとデータは一緒に保存されません(MyISAM myz / mydインデックスやデータ分離など)
    • クラスター化インデックスクエリは、非クラスター化インデックスクエリよりも高速です
      ここに画像の説明を挿入
    • B + Treeは、B-Treeよりも多くのデータを格納できます
    • 主キーを使用して自動インクリメントの形状を変更することに注意してください
      ここに画像の説明を挿入

1.1MyISAMインデックス

1.2InnoDBインデックス

InnoDBデータテーブルを作成する

CREATE TABLE `abc_innodb`
(
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a`  int(11)     DEFAULT NULL,
  `b`  int(11)     DEFAULT NULL,
  `c`  varchar(10) DEFAULT NULL,
  `d`  varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_abc` (`a`, `b`, `c`)
) ENGINE = InnoDB;

INSERT INTO abc_innodb(a,b,c) VALUES (1,4,2);
INSERT INTO abc_innodb(a,b,c) VALUES (8,5,6);
INSERT INTO abc_innodb(a,b,c) VALUES (6,7,5);
INSERT INTO abc_innodb(a,b,c) VALUES (4,7,3);
INSERT INTO abc_innodb(a,b,c) VALUES (4,7,2);
INSERT INTO abc_innodb(a,b,c) VALUES (9,2,1);
INSERT INTO abc_innodb(a,b,c) VALUES (19,6,2);
INSERT INTO abc_innodb(a,b,c) VALUES (10,1,9);

1.3左端のマッチング原則

最左前缀匹配原则和联合索引的索引存储结构和检索方式是有关系的。

在组合索引树中,最底层的叶子节点按照第一列a列从左到右递增排列,但是b列和c列是无序的,b列只有在a列值相等的情况下小范围内递增有序,而c列只能在a,b两列相等的情况下小范围内递增有序。

就像上面的查询,B+树会先比较a列来确定下一步应该搜索的方向,往左还是往右。如果a列相同再比较b列。但是如果查询条件没有a列,B+树就不知道第一步应该从哪个节点查起。

可以说创建的idx_abc(a,b,c)索引,相当于创建了(a)、(a,b)(a,b,c)三个索引。、

组合索引的最左前缀匹配原则:使用组合索引查询时,mysql会一直向右匹配直至遇到范围查询(>、<、between、like)就停止匹配

2.インデックスタイプ

2.1主キーインデックス

各InnodDBテーブルにはクラスター化インデックスがあります。クラスター化インデックスはB + Treeを使用して構築され、リーフノードに格納されるデータはレコードの行全体です。一般に、クラスター化インデックスは主キーインデックスと同等です。テーブルが主キーインデックスを作成しない場合、InnoDBは自動的にRowIDフィールドを作成して、クラスター化インデックスを構築します。自動インデックス作成の具体的なルールは次のとおりです。


 1. 在表上定义主键 PRIMARY KEY,InnoDB将主键索引引用聚簇索引
 2.如果没有定义主键,InooDB会选择第一个不为NULL的唯一索引列用作聚簇索引 
 3.如果以上两个都没有,InnoDB会使用一个6 byte长整型的随机字段ROWID字段构建聚簇
 索引。该ROWID字段会在插入新行时自动递增

クラスタ化インデックスを除くすべてのインデックスは、セカンダリインデックスと呼ばれます。InnoDBでは、補助インデックスのリーフノードに格納されているデータが行の主キー値です。取得時に、InnoDBはこの主キー値を使用して、クラスター化インデックスの行を検索します。

主キーインデックスのリーフノードはデータ行を格納し、二次インデックスは主キー値のみを格納します。

2.2通常のインデックス

MySqlの基本的なインデックスタイプであり、制限はありません。定義されたインデックス列に重複する値とnull値を挿入できます。

2.3一意のインデックス

インデックス列の値は一意である必要がありますが、null値は許可されます

2.4フルテキストインデックス

フルテキストインデックスは、テキストタイプのCHAR、VARCHAR、TEXTタイプのフィールドでのみ作成できます。最短の長さが比較的大きい場合、通常のインデックスを作成すると、ファジークエリのような効率が比較的低くなり、フルテキストを作成できます。 -MyISAMおよびInnoDBのテキストインデックス。フルテキストインデックスを使用します。

2.5空間インデックス

Mysqlは、5.7以降の空間インデックスをサポートし、OpenGIS幾何データモデルをサポートします。MySqlは、空間インデックスに関してOpenGIS幾何データモデルのルールに従います。

2.6プレフィックスインデックス

CHAR、VARCHAR、TEXTなどのテキストタイプでインデックスを作成する場合、インデックス列の長さを指定できますが、数値タイプを判別することはできません。

2.7単一列インデックス

単一のフィールドによって作成されたインデックス(テーブルには最大16個のインデックスを含めることができ、最大バイト長は256です)

2.8複合インデックス(ジョイントインデックス)

複数のフィールドで構成されるインデックスは、複合インデックスと呼ばれます(複合インデックスの使用は、左端のプレフィックス一致の原則に従う必要があります。通常、複合インデックスは、条件が許せば、複数の単一列インデックスを置き換えるために使用されます)

ジョイントインデックスは、インデックスを作成するときに、ジョイントインデックスを複数の単一列インデックスで使用できるかどうかを判断するようにしてください。ジョイントインデックスを使用すると、スペースが節約されるだけでなく、インデックスカバレッジが使いやすくなります。

インデックスに登録されたフィールドが多いほど、クエリによって返されるデータを満たすのが簡単になると想像してみてください。たとえば、ジョイントインデックス(a_b_c)は、a、a_b、a_b_cの3つのインデックスを持つことと同じです。これでスペースが節約されますか?もちろん、節約されるスペースは3つのインデックス(a、a_b、a_b_c)の3倍ではありません。インデックスツリーのデータは変更されていませんが、インデックスデータフィールドのデータは実際に保存されています。

ジョイントインデックス作成の原則は、ジョイントインデックスを作成するときは、頻繁に使用する列と高度に差別化された列を前面に配置する必要があります。頻繁に使用することはインデックスの使用率が高く、識別率が高いことはフィルタリングの粒度が大きいことを意味します。考慮する必要のある最適化シナリオは、クエリとして返されることが多いフィールドのジョイントインデックスに追加することもできます。ジョイントインデックスにフィールドを追加してカバーインデックスを使用する場合は、ジョイントインデックスの使用に注意してください。

ジョイントインデックスの使用

  1. マージできる単一列インデックスが複数あるかどうかを検討します。ある場合は、結合インデックスとして複数の単一列インデックスを作成します。
  2. 現在、戻りフィールドとして頻繁に使用されるすべての列があります。この時点で、現在の列を現在の既存のインデックスに追加して、クエリステートメントでカバーインデックスを使用できるかどうかを検討できます。

ここに画像の説明を挿入

select * from abc_innodb where a = 13 and b = 16 and c = 4;

ここに画像の説明を挿入

CREATE TABLE `abc_innodb`
(
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a`  int(11)     DEFAULT NULL,
  `b`  int(11)     DEFAULT NULL,
  `c`  varchar(10) DEFAULT NULL,
  `d`  varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_abc` (`a`, `b`, `c`)
) ENGINE = InnoDB;

2.9カバーインデックス(インデックス構造ではない)

カバーリングインデックスは、それがインデックス構造であると言っているのではなく、カバーリングインデックスは非常に一般的な最適化方法です。補助インデックスを使用する場合、データを取得するのと同等の主キー値しか取得できないため、主キーに基づいて主キーインデックスをクエリしてから、データを取得する必要があります。しかし、次の状況を想像してみてください。上記のabc_innodbテーブルの複合インデックスをクエリするときに、abcフィールドのみが必要な場合、それは、.tableを返す必要なしに複合インデックスのリーフノードを直接返すことができることを意味します。この状況がカバーインデックスです。
ここに画像の説明を挿入

3.インデックスデータ構造

3.1ハッシュテーブル

ハッシュテーブル、JavaのHashMap、TreeMapは、データをキーと値のペアの形式で格納するハッシュテーブルの構造です。ハッシュテーブルを使用してテーブルデータを格納します。Keyはインデックス列を格納でき、Valueは行レコードまたは行ディスクアドレスを格納できます。ハッシュテーブルの同等のクエリは非常に効率的で、時間計算量はO(1)ですが、範囲高速検索はサポートされておらず、範囲検索はテーブル全体をスキャンするだけです
(明らかに、これは頻繁な検索や範囲には適していません。検索に使用されるデータベースインデックス。)

3.2二分木検索

次の図は
ここに画像の説明を挿入
二分木の例です。二分木の特徴:各ノードには最大2つのブランチがあり、左から右へのサブツリーと右のサブツリーのデータ順序は小さいです。
この機能は、各検索を半分にしてIOの数を減らすことができるようにするためのものですが、二分木は、発生したくない状況が発生しやすいため、最初のルートノードの値のテストです。この機能。「木は分岐していません。」それは不快です

以下に示すように

ここに画像の説明を挿入

3.3平衡二分木

平衡二分木は二分法を採用しており、二分木の特徴に加えて、二分木の左右のサブツリーのレベルが最大で異なるという主な特徴があります。データの挿入と削除の際、二分木のバランスは左利き/右利きの操作によって維持されます。、左のサブツリーが非常に高く、右のサブツリーが非常に短い状況はありません。

平衡二分木クエリを使用した場合のパフォーマンスは二分探索法に近く、時間計算量はO(log2n)クエリid = 6であり、2つのIO操作のみが必要です
ここに画像の説明を挿入
。平衡二分木の問題:

  1. 時間計算量は木の高さに関係しています。ツリーの回数だけ取得する必要があり、各ノードの読み取りはディスクIO操作に対応します。叔父の身長は、データが照会されるたびのディスクIO操作の数と同じです。各ディスクが本の剣を探すのに10msかかります。テーブルデータの量が多いと、クエリのパフォーマンスが低下します。(100万のデータ量、log2nは20ディスクIO時間20 * 10 = 0.2msにほぼ等しい)
  2. 平衡二分木は範囲クエリとクイック検索をサポートしていません。範囲クエリはルートノードから複数回トラバースする必要があり、クエリの効率は高くありません。

3.4 Bツリー(バイナリツリーの再構築)

mysql的数据是存储在磁盘文件中的,查询处理数据时,需要先把磁盘中的数据加载到内存中,磁盘IO操作非常耗时
,所以我们优化的重点就是减少磁盘的IO操作。访问二叉树的每个节点就会发生一次IO如果想要减少磁盘IO操作就要
降低树的高度。

假如key为bigint=8byte 每个节点有两个指针,每个指针为4个byte 一个节点占用的空间16个byte(8+4*2=16)

因为每次在Mysqk的InnoDB存储引擎一次IO会读取的一页默认(16kb)的数据量,而二叉树一次IO有效数据量只有16byte
,空间利用率极低为了最大化利用一次IO空间一个简单的想法是在每个节点存储多个元素 在每个节点尽可能多的存储数
据。每个节点可以存储1000个索引(16k/16=1000),这样就将二叉树改造成了多叉树,通过增加树的叉树,将树
从高变成了矮胖。构建一百万条数据,树的高度只需要2层就可以了(1000*1000=100万) 也就说只需要进行两次IO
操作就能拿到数据 磁盘IO次数减少查询数据的效率也就提高了

B树是一种多叉平衡查找树

 1. B树的节点中存储着多个元素,每个内节点有多个分叉
 2. 节点中的元素包含键值和数据,节点中的键值从大到小进行排列也就是说在所有节点都能存储数据
 3. 父节点当中的元素不会出现在子节点
 4. 所有叶子节点都位于同一层,叶子节点具有相同的的深度,叶节点之间没有指针连接

ここに画像の説明を挿入
たとえば、bツリーのデータをクエリします。

10に等しい値でデータをクエリするとします。クエリパスディスクブロック1->ディスクブロック2->ディスクブロック5。

最初のディスクIO:ディスクブロック1をメモリにロードし、メモリの最初から10 <15の比較をトラバースして、左に移動し、ディスクブロック2をアドレス指定します。

2番目のディスクIO:ディスクブロック2をメモリにロードし、メモリ内の最初から比較をトラバースして、7 <10、ディスク内のディスクブロック5を見つけます。

3番目のディスクIO:ディスクブロック5をメモリにロードし、最初からメモリをトラバースして比較します。10= 10、10を検索し、データをフェッチします。データによって格納された行レコードの場合、データをフェッチすると、クエリは終了します。ディスクアドレスが保存されている場合は、ディスクアドレスに従ってデータをディスクから取得する必要があり、クエリは終了します。

バイナリ平衡探索木と比較すると、検索プロセス全体で、データ比較の数は大幅に減少しませんが、ディスクIOの数は大幅に減少します。同時に、比較はメモリ内で実行されるため、時間のかかる比較はごくわずかです。ほとんどのアプリケーションシナリオを満たすために、Bツリーの高さは通常2〜3層であるため、Bツリーを使用してインデックスを作成すると、クエリの効率を向上させることができます。

プロセスは次のとおりです。

ここに画像の説明を挿入
問題は、Bツリーがまだ変換できることです


 1. B树同样不支持范围查询的快速查找 就意味着当你进行范围查找时还是会从根节点去挨个遍历 可想而知其
 查询效率还是很低
 2. 如果data存储的是行记录,行的大小随着列数的增多,所占用的空间就会变大,这时一个页中可存储的数据量
 就会变少,树相应就会变高,磁盘IO次数就会变多

3.5 B +ツリー(B +ツリー、Bツリーの変換)

B + TreeはB-treeのアップグレードバージョンです。MysqlはB-treeに基づいて変換を続け、B + Treeを使用してインデックスを作成します。B + TreeBツリーの主な違いは、非リーフノードがデータを格納するかどうかです。


 1. B树:非叶子节点和叶子节点都会存储数据
 2. B+Tree :只有叶子节点会存储数据,非叶子节点只存储键值。叶子节点之间使用双向指针来连接,最底层的
 叶子节点形成了一个双向有序的列表

ここに画像の説明を挿入
B + Treeの一番下のリーフノードには、すべてのインデックスアイテムが含まれています。この図から、B + Treeがデータを検索する場合、データは最下位のリーフノードに格納されているため、検索でリーフノードを取得してデータをクエリする必要があるたびに、データをクエリする必要があることがわかります。各ディスクIOはツリーの高さに直接関係しますが、一方で、データは子ノードに配置されるため、インデックスのディスクブロックロックによって格納されるインデックスの数は、Bツリーに比べて増加します。 、B + Treeのツリーの高さは、理論的にはB-treeツリーのツリーの高さよりも短くなります。インデックスカバレッジも存在します。インデックス内のデータは、現在のクエリステートメントに必要なすべてのデータを満たしています。現時点では、を見つけるだけで済みます。インデックスを作成してすぐに返します。、下部のリーフノードを取得する必要はありません。

3.5.1例:同等のクエリ

9に等しい値でデータをクエリするとします。クエリパスディスクブロック1->ディスクブロック2->ディスクブロック6

最初のディスクIO:ディスクブロック1をメモリにロードし、メモリの最初から比較をトラバースし、9 <15左に移動して、ディスクブロック2をアドレス指定するディスクに移動します。

2番目のディスクIO:ディスクブロック2をメモリにロードし、メモリ内の最初から7 <9 <12をトラバースして比較し、ディスクアドレス指定でディスクブロック6を見つけます。

3番目のディスクIO:ディスクブロック6をメモリにロードし、メモリ内で最初から比較し、3番目のインデックスで9を見つけてデータをフェッチします。データの行レコードが保存されている場合は、データをフェッチしてクエリを終了します。ストレージがディスクアドレスの場合、ディスクアドレスに従ってディスクからデータをフェッチする必要もあり、クエリは終了します(ここでの違いは、InnoDBに格納されているデータが行データであり、ディスクアドレスが格納されていることです。 MyIsamで)
以下に示すように

ここに画像の説明を挿入

3.5.2範囲クエリ

9から26までのデータを検索するとします。検索パスは、ディスクブロック1->ディスクブロック2->ディスクブロック6->ディスクブロック7です。

まず、値が9のデータを見つけ、値が9のデータを結果セットにキャッシュします。この手順は、前の同等のクエリプロセスと同じで、3つのディスクIO操作が発生しました。

15を見つけた後、一番下のリーフノードは順序付きリストです。ディスクブロック6とキー値9から開始し、逆方向にトラバースして、条件を満たすすべてのデータをフィルタリングします。

4番目のディスクIO:ディスクブロック6のディスクアドレス指定への後続のポインタに従ってディスクブロック7を見つけ、ディスク7をメモリにロードし、メモリ内の比較を最初からトラバースします(9 <25 <26、9 <26 <= 26)。データを結果セットにキャッシュする

主キーは一意であるため(後でデータが<= 26になることはありません)、終了するクエリを探し続けて結果を返す必要はありません。

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_43565087/article/details/109200610