MySQL の高度な - インデックスの最適化とクエリの最適化 (1)

インデックスの最適化

1. データの準備

500,000 のエントリが学生リストに挿入され、10,000 のエントリがクラス リストに挿入されます。

テーブルの作成

CREATE TABLE `class` (
	`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
	`className` VARCHAR ( 30 ) DEFAULT NULL,
	`address` VARCHAR ( 40 ) DEFAULT NULL,
	`monitor` INT NULL,
	PRIMARY KEY ( `id` ) 
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
CREATE TABLE `student` (
	`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
	`stuno` INT NOT NULL,
	`name` VARCHAR ( 20 ) DEFAULT NULL,
	`age` INT ( 3 ) DEFAULT NULL,
	`classId` INT ( 11 ) DEFAULT NULL,
	PRIMARY KEY ( `id` ) #CONSTRAINT `fk_class_id` FOREIGN KEY (`classId`) REFERENCES `t_class` (`id`)

) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;

パラメータの設定

  • コマンドオン: 関数設定の作成を許可します:
set global log_bin_trust_function_creators=1; # 不加global只是当前窗口有效。

関数の作成

各データが異なることを確認してください。

#随机产生字符串

DELIMITER //
CREATE FUNCTION rand_string ( n INT ) RETURNS VARCHAR ( 255 ) BEGIN
	DECLARE
		chars_str VARCHAR ( 100 ) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
	DECLARE
		return_str VARCHAR ( 255 ) DEFAULT '';
	DECLARE
		i INT DEFAULT 0;
	WHILE
			i < n DO
			
			SET return_str = CONCAT(
				return_str,
			SUBSTRING( chars_str, FLOOR( 1+RAND ()* 52 ), 1 ));
		
		SET i = i + 1;
		
	END WHILE;
	RETURN return_str;
	
END // 
DELIMITER;#假如要删除
#drop function rand_string;

クラス番号をランダムに生成する

#用于随机产生多少到多少的编号

DELIMITER //
CREATE FUNCTION rand_num ( from_num INT, to_num INT ) RETURNS INT ( 11 ) BEGIN
	DECLARE
		i INT DEFAULT 0;
	
	SET i = FLOOR(
		from_num + RAND()*(
			to_num - from_num + 1 
		));
	RETURN i;
	
END // 
DELIMITER;#假如要删除
#drop function rand_num;

ストアド プロシージャを作成する

#创建往stu表中插入数据的存储过程

DELIMITER //
CREATE PROCEDURE insert_stu ( START INT, max_num INT ) BEGIN
	DECLARE
		i INT DEFAULT 0;
	
	SET autocommit = 0;#设置手动提交事务
	REPEAT#循环
		
		SET i = i + 1;#赋值
		INSERT INTO student ( stuno, NAME, age, classId )
		VALUES
			((
					START + i 
					),
				rand_string ( 6 ),
				rand_num ( 1, 50 ),
			rand_num ( 1, 1000 ));
		UNTIL i = max_num 
	END REPEAT;
	COMMIT;#提交事务
	
END // 
DELIMITER;#假如要删除
#drop PROCEDURE insert_stu;

クラステーブルにデータを挿入するストアドプロシージャを作成する

#执行存储过程,往class表添加随机数据

DELIMITER //
CREATE PROCEDURE `insert_class` ( max_num INT ) BEGIN
	DECLARE
		i INT DEFAULT 0;
	
	SET autocommit = 0;
	REPEAT
			
			SET i = i + 1;
		INSERT INTO class ( classname, address, monitor )
		VALUES
			(
				rand_string ( 8 ),
				rand_string ( 10 ),
			rand_num ( 1, 100000 ));
		UNTIL i = max_num 
	END REPEAT;
	COMMIT;
	
END // 
DELIMITER;#假如要删除
#drop PROCEDURE insert_class;

ストアド プロシージャを呼び出す

クラス

#执行存储过程,往class表添加1万条数据
CALL insert_class(10000);

スチュ

#执行存储过程,往stu表添加50万条数据
CALL insert_stu(100000,500000);

テーブルのインデックスを削除する

ストアド プロシージャを作成する

DELIMITER //
CREATE PROCEDURE `proc_drop_index` (
	dbname VARCHAR ( 200 ),
	tablename VARCHAR ( 200 )) BEGIN
	DECLARE
		done INT DEFAULT 0;
	DECLARE
		ct INT DEFAULT 0;
	DECLARE
		_index VARCHAR ( 200 ) DEFAULT '';
	DECLARE
		_cur CURSOR FOR SELECT
		index_name 
	FROM
		information_schema.STATISTICS 
	WHERE
		table_schema = dbname 
		AND table_name = tablename 
		AND seq_in_index = 1 
		AND index_name <> 'PRIMARY';#每个游标必须使用不同的declare continue handler for not found set done=1来控制游标的结束
	DECLARE
		CONTINUE HANDLER FOR NOT FOUND 
		SET done = 2;#若没有数据返回,程序继续,并将变量done设为2
	OPEN _cur;
	FETCH _cur INTO _index;
	WHILE
			_index <> '' DO
			
			SET @str = CONCAT( "drop index ", _index, " on ", tablename );
		PREPARE sql_str 
		FROM
			@str;
		EXECUTE sql_str;
		DEALLOCATE PREPARE sql_str;
		
		SET _index = '';
		FETCH _cur INTO _index;
		
	END WHILE;
	CLOSE _cur;
	
END // 
DELIMITER;

ストアドプロシージャを実行する

CALL proc_drop_index("dbname","tablename");

2. インデックス失敗のケース

2.1 完全値一致私のお気に入り

2.2 ベストレフトプレフィックスルール

拡張子:Alibaba「Java開発マニュアル」の
インデックスファイルはB-Treeの左端のプレフィックスマッチング機能を持っており、左端の値が決まらないとこのインデックスは使用できません。

2.3 主キーの挿入順序

ここに画像の説明を挿入します

このとき、主キー値が 9 の別のレコードを挿入すると、その挿入位置は次のようになります。

ここに画像の説明を挿入します

しかし、このデータ ページはすでにいっぱいです。再度挿入する場合はどうすればよいですか? 現在のページを 2 つのページに分割し、このページ内のいくつかのレコードを新しく作成したページに移動する必要があります。ページ分割とレコード シフトとは何を意味しますか? 意味: パフォーマンスの低下! したがって、このような不要なパフォーマンスの低下を可能な限り回避したい場合は、挿入されたレコードの主キーの値を順番に増やして、そのようなパフォーマンスの低下が発生しないようにするのが最善です。したがって、主キーに AUTO_INCREMENT を持たせ、テーブルの主キーを手動で挿入するのではなく、ストレージ エンジンが独自に生成するようにすることをお勧めします。例: person_info テーブル:

CREATE TABLE person_info (
	id INT UNSIGNED NOT NULL AUTO_INCREMENT,
	NAME VARCHAR ( 100 ) NOT NULL,
	birthday DATE NOT NULL,
	phone_number CHAR ( 11 ) NOT NULL,
	country VARCHAR ( 100 ) NOT NULL,
	PRIMARY KEY ( id ),
KEY idx_name_birthday_phone_number ( NAME ( 10 ), birthday, phone_number ) 
);

カスタム主キー列 ID にはAUTO_INCREMENT 属性があり、レコードの挿入時にストレージ エンジンが自動インクリメントされた主キー値を自動的に入力します。このような主キーはほとんどスペースをとらず、順次に書き込まれ、ページ分割が削減されます。

2.4 計算、関数、型変換(自動または手動)によるインデックスの失敗

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';

インデックスの作成

CREATE INDEX idx_name ON student(NAME);

タイプ 1: インデックスの最適化が有効になる

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';

2 番目のタイプ: インデックス最適化の失敗

mysql>  EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 498917 |   100.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 2 warnings (0.00 sec)
mysql> SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
+--------+--------+--------+------+---------+
| id     | stuno  | name   | age  | classId |
+--------+--------+--------+------+---------+
|    399 | 100399 | ABcKtL |   24 |     198 |
|  16470 | 116470 | ABcJlg |   47 |     251 |
|  27952 | 127952 | ABcJmj |   10 |     397 |
|  54809 | 154809 | aBClvu |   37 |     495 |
|  61540 | 161540 | abclUS |   30 |     374 |
|  83160 | 183160 | aBCjpV |   34 |     593 |
|  89664 | 189664 | aBCjmJ |   34 |     350 |
| 240498 | 340498 | aBCksj |   41 |     491 |
| 245214 | 345214 | abciJU |   23 |     568 |
| 258459 | 358459 | aBClxC |   23 |     566 |
| 300169 | 400169 | aBClxC |   21 |     412 |
| 300328 | 400328 | ABcJnn |   27 |     870 |
| 324684 | 424684 | aBCkrg |   30 |     566 |
| 416907 | 516907 | ABcHgI |   46 |     607 |
| 424459 | 524459 | abclVU |   39 |     192 |
| 445547 | 545547 | ABcJpw |   16 |     180 |
| 454772 | 554772 | AbCHFf |   37 |     313 |
| 466466 | 566466 | abckRF |   26 |     725 |
| 475708 | 575708 | abclWY |    4 |     415 |
| 486611 | 586611 | ABcLwb |   41 |     948 |
| 490152 | 590152 | ABcHfC |   24 |     717 |
+--------+--------+--------+------+---------+
21 rows in set, 1 warning (0.12 sec)

type は「ALL」で、インデックスが使用されないことを示します。

2.5 型変換によりインデックスが失敗する

次の SQL ステートメントのうち、インデックスを使用できるものはどれですか。(名前フィールドにインデックスが設定されていると仮定します)

# 未使用到索引
mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name=123;
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | idx_name      | NULL | NULL    | NULL | 498917 |    10.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 4 warnings (0.00 sec)
# 使用到索引
mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name='123';
+----+-------------+---------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table   | partitions | type | possible_keys | key      | key_len | ref   | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+----------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | student | NULL       | ref  | idx_name      | idx_name | 63      | const |    1 |   100.00 | NULL  |
+----+-------------+---------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 2 warnings (0.00 sec)
  • name=123 で型変換が発生し、インデックスが無効です。

2.6 範囲条件の右側の列インデックスが不正です

ALTER TABLE student DROP INDEX idx_name;
ALTER TABLE student DROP INDEX idx_age;
ALTER TABLE student DROP INDEX idx_age_classid;
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ;
create index idx_age_name_classid on student(age,name,classid);
  • ステートメントの最後に範囲クエリ条件を配置します。
mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age=30 AND student.name =
    -> 'abc' AND student.classId>20 ;
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys        | key                  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | student | NULL       | range | idx_age_name_classid | idx_age_name_classid | 73      | NULL |    1 |   100.00 | Using index condition |
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
1 row in set, 2 warnings (0.00 sec)

2.7 等しくない (!= または <>) インデックスが無効です

2.8 使用できるインデックスは null ですが、インデックスは null ではありません。

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NULL;

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NOT NULL;

2.9 ワイルドカード文字 % で始まる類似インデックスは無効です

拡張:Alibaba「Java開発マニュアル」
【必須】左あいまい・全あいまいページ検索は厳禁ですので、必要に応じて検索エンジンをご利用ください。

2.10 OR の前後にインデックスのない列があり、インデックスが無効です。

# 未使用到索引
mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR classid = 100;
+----+-------------+---------+------------+------+----------------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys        | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+----------------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | idx_age_name_classid | NULL | NULL    | NULL | 498917 |    11.88 | Using where |
+----+-------------+---------+------------+------+----------------------+------+---------+------+--------+----------+-------------+
1 row in set, 2 warnings (0.00 sec)
# 使用到索引
mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR name = 'Abel';
+----+-------------+---------+------------+------+----------------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys        | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+----------------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | idx_age_name_classid | NULL | NULL    | NULL | 498917 |    11.88 | Using where |
+----+-------------+---------+------------+------+----------------------+------+---------+------+--------+----------+-------------+
1 row in set, 2 warnings (0.00 sec)

2.11 データベースおよびテーブルの文字セットは一律に utf8mb4 を使用します。

utf8mb4(バージョン5.5.3以降でサポート)を統一することで互換性が向上し、文字セットを統一することで文字セット変換による文字化けを回避できます。比較する前に異なる文字セットを変換する必要があるため、インデックスエラーが発生します。

3. 関連クエリの最適化

3.1 データの準備

CREATE TABLE IF NOT EXISTS `type` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
);
#图书
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`)
);

#向分类表中添加20条记录
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));

#向图书表中添加20条记录
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

3.2 左外部結合を使用する

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | type  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   20 |   100.00 | NULL                                               |
|  1 | SIMPLE      | book  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   20 |   100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 2 warnings (0.00 sec)

結論: 型にはすべてがある

インデックスの最適化を追加する

ALTER TABLE book ADD INDEX Y ( card); #【被驱动表】,可以避免全表扫描

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref                  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+------+----------+-------------+
|  1 | SIMPLE      | type  | NULL       | ALL  | NULL          | NULL | NULL    | NULL                 |   20 |   100.00 | NULL        |
|  1 | SIMPLE      | book  | NULL       | ref  | Y             | Y    | 4       | atguigudb2.type.card |    1 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)

2 行目の型が ref に変更され、行も明らかな最適化に変更されていることがわかります。これは、左結合プロパティによって決定されます。LEFT JOIN
条件は、右側のテーブルから行を検索する方法を決定するために使用されます。左側にすべての行が存在する必要があるため、右側がキー ポイントであり、インデックスを作成する必要があります。

ALTER TABLE `type` ADD INDEX X (card); #【驱动表】,无法避免全表扫描

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
+----+-------------+-------+------------+-------+---------------+------+---------+----------------------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref                  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+------+---------+----------------------+------+----------+-------------+
|  1 | SIMPLE      | type  | NULL       | index | NULL          | X    | 4       | NULL                 |   20 |   100.00 | Using index |
|  1 | SIMPLE      | book  | NULL       | ref   | Y             | Y    | 4       | atguigudb2.type.card |    1 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+----------------------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)
DROP INDEX Y ON book;

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | type  | NULL       | index | NULL          | X    | 4       | NULL |   20 |   100.00 | Using index                                        |
|  1 | SIMPLE      | book  | NULL       | ALL   | NULL          | NULL | NULL    | NULL |   20 |   100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 2 warnings (0.00 sec)

3.3 内部結合を使用する

drop index X on type;
drop index Y on book;

内部結合で置換 (MySQL が自動的にドライバー テーブルを選択します)

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | type  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   20 |   100.00 | NULL                                               |
|  1 | SIMPLE      | book  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   20 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 2 warnings (0.00 sec)

インデックスの最適化を追加する

ALTER TABLE book ADD INDEX Y ( card);

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref                  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+------+----------+-------------+
|  1 | SIMPLE      | type  | NULL       | ALL  | NULL          | NULL | NULL    | NULL                 |   20 |   100.00 | NULL        |
|  1 | SIMPLE      | book  | NULL       | ref  | Y             | Y    | 4       | atguigudb2.type.card |    1 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)
ALTER TABLE type ADD INDEX X (card);

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;
+----+-------------+-------+------------+-------+---------------+------+---------+----------------------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref                  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+------+---------+----------------------+------+----------+-------------+
|  1 | SIMPLE      | type  | NULL       | index | X             | X    | 4       | NULL                 |   20 |   100.00 | Using index |
|  1 | SIMPLE      | book  | NULL       | ref   | Y             | Y    | 4       | atguigudb2.type.card |    1 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+----------------------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)

それから:

DROP INDEX X ON `type`;

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM TYPE INNER JOIN book ON type.card=book.card;
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref                  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+------+----------+-------------+
|  1 | SIMPLE      | TYPE  | NULL       | ALL  | NULL          | NULL | NULL    | NULL                 |   20 |   100.00 | NULL        |
|  1 | SIMPLE      | book  | NULL       | ref  | Y             | Y    | 4       | atguigudb2.TYPE.card |    1 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)

それから:

ALTER TABLE `type` ADD INDEX X (card);

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM `type` INNER JOIN book ON type.card=book.card;
+----+-------------+-------+------------+-------+---------------+------+---------+----------------------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref                  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+------+---------+----------------------+------+----------+-------------+
|  1 | SIMPLE      | type  | NULL       | index | X             | X    | 4       | NULL                 |   20 |   100.00 | Using index |
|  1 | SIMPLE      | book  | NULL       | ref   | Y             | Y    | 4       | atguigudb2.type.card |    1 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+----------------------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)

3.4 結合文の原理

インデックスのネストループ結合

EXPLAIN SELECT * FROM t1 STRAIGHT_JOIN t2 ON (t1.a=t2.a);

join ステートメントを直接使用する場合、MySQL オプティマイザは駆動テーブルとしてテーブル t1 または t2 を選択する可能性があり、これは SQL ステートメントの分析の実行プロセスに影響を与えます。したがって、実行中のパフォーマンスの問題の分析を容易にするために、straight_join オプティマイザが指定した方法でのみ結合するように、代わりに MySQL に固定接続方法を使用してクエリを実行するように依頼しました。このステートメントでは、t1 は駆動テーブル、t2 は被駆動テーブルです。

このステートメントでは、駆動テーブル t2 のフィールド a にインデックスがあり、結合プロセスではこのインデックスが使用されることがわかります。そのため、このステートメントの実行フローは次のようになります。

  1. テーブル t1 からデータ R の行を読み取ります。
  2. データ行 R からフィールド a を取り出し、テーブル t2 で検索します。
  3. テーブル t2 の条件を満たす行を取り出し、結果セットの一部として R を含む行を形成します。
  4. テーブル t1 の終わりでループが終了するまで、手順 1 ~ 3 を繰り返します。

このプロセスでは、最初にテーブル t1 を走査し、次にテーブル t2 に移動して、テーブル t1 から取得したデータの各行の a 値に基づいて条件を満たすレコードを検索します。形式的には、このプロセスはプログラムを作成するときのネストされたクエリに似ており、駆動テーブルのインデックスを使用できるため、「インデックス ネスト ループ結合」 (略して NLJ) と呼ばれます。

対応するフローチャートは次のとおりです。

ここに画像の説明を挿入します

このプロセスでは次のことが行われます。

  1. ドライバー テーブル t1 でテーブル全体のスキャンが実行されました。このプロセスでは 100 行のスキャンが必要でした。
  2. R の各行について、ツリー検索プロセスを使用して、フィールドに基づいてテーブル t2 が検索されます。構築するデータは 1 対 1 に対応しているため、各検索プロセスでスキャンされるのは 1 行のみで、合計 100 行がスキャンされます。
  3. したがって、実行プロセス全体でスキャンされる合計ライン数は 200 になります。

2 つの結論:

  1. join ステートメントを使用すると、SQL ステートメントを実行するために強制的に複数の単一テーブルに分割するよりもパフォーマンスが向上します。
  2. join ステートメントを使用する場合は、小さなテーブルを駆動テーブルにする必要があります。

単純なネストループ結合
ブロック ネストループ結合

ここに画像の説明を挿入します

実行フローチャートは次のようになります。

ここに画像の説明を挿入します

どちらのテーブルを駆動テーブルとして使用するかを決定する際には、2 つのテーブルをそれぞれの条件に従ってフィルタリングする必要があります。フィルタリングが完了した後、結合に参加する各フィールドの合計データ量が計算されます。データの少ないテーブルボリュームはドライバーテーブルとしての「小さなテーブル」です。

要約する

  • ドリブン テーブルの JOIN フィールドにインデックスが作成されていることを確認します。
  • JOIN が必要なフィールドでは、データ型が完全に一貫している必要があります。
  • LEFT JOIN の場合、小さなテーブルを駆動テーブルとして選択します大表作为被驱动表外側のループの数を減らします。
  • INNER JOIN の場合、MySQL は自動的に結合します小结果集的表选为驱动表MySQL 最適化戦略を信頼することを選択します。
  • 複数のテーブルを直接関連付けることができる場合は、サブクエリを使用せずに直接関​​連付けるようにしてください。(クエリ数を減らす)
  • サブクエリの使用は推奨されません。サブクエリ SQL を分割してプログラムを複数のクエリと組み合わせるか、サブクエリの代わりに JOIN を使用することをお勧めします。
  • 派生テーブルではインデックスを作成できません。

4. サブクエリの最適化

MySQL では、バージョン 4.1 以降のサブクエリをサポートしています。サブクエリを使用すると、SELECT ステートメントのネストされたクエリを実行できます。つまり、1 つの SELECT クエリの結果が別の SELECT ステートメントの条件として機能します。サブクエリは、論理的に複数の手順を一度に完了する必要がある多くの SQL 操作を完了できます

サブクエリは MySQL の重要な機能であり、SQL ステートメントを通じてより複雑なクエリを実装するのに役立ちます。ただしサブクエリの実行効率は高くない

① サブクエリを実行する場合、MySQL は内部クエリ ステートメントのクエリ結果を格納する一時テーブルを作成する必要があり、その後、外部クエリ ステートメントで一時テーブルのレコードをクエリします。クエリが完了すると、これらの一時テーブルは取り消されます。これにより、CPU と IO リソースが過剰に消費され、低速なクエリが大量に生成されます。

② サブクエリの結果セットに格納される一時テーブルは、メモリ一時テーブルであってもディスク一時テーブルであってもインデックスを持たないため、クエリのパフォーマンスにある程度の影響を受けます。

③ より大きな結果セットを返すサブクエリの場合、クエリのパフォーマンスへの影響は大きくなります。MySQL では、サブクエリの代わりに結合 (JOIN) クエリを使用できます。結合クエリは一時テーブルを作成する必要がなく、サブクエリよりも高速であり、クエリでインデックスを使用するとパフォーマンスが向上します。

結論: NOT IN や NOT EXISTS は使用せず、代わりに LEFT JOIN xxx ON xx WHERE xx IS NULL を使用してください。

おすすめ

転載: blog.csdn.net/qq_51495235/article/details/133087565