インデックスの無効化の問題が原因のMySQLテーブルフィールドの文字セット

インデックスの無効化の問題が原因のMySQLテーブルフィールドの文字セット

転送元:インデックスの無効化の問題が原因のMySQLテーブルフィールドの文字セット

1.概要

昨日、クラスメートのMySQLマシンでこのような問題を発見しました。MySQLの2つのテーブルを結合したままにした場合、実行プランは、フルテーブルスキャンを使用するテーブルがあり、フルテーブル内のレコードの約100万行をスキャンすることを示しましたSQLが登場し、データベースはほとんど使用できなくなりました。MySQLバージョンは公式の5.7.12です。

2.問題を再現する

まず、テーブル構造とテーブルレコードは次のとおりです。

mysql> show create table t1\G
*************** 1. row ***************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`code` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> show create table t2\G
*********** 1. row *******************
Table: t2
Create Table: CREATE TABLE `t2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`code` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

mysql> select * from t1;
+-+——+———————————-+
| id | name | code |
+-+——+———————————-+
| 1 | aaaa | ...... |
| 2 | bbbb | ...... |
| 3 | cccc | ...... |
| 4 | dddd | ...... |
| 5 | eeee | ...... |
+-+——+———————————-+
5 rows in set (0.00 sec)

mysql> select * from t2;
+-+——+———————————-+
| id | name | code |
+-+——+———————————-+
| 1 | aaaa | ...... |
| 2 | bbbb | ...... |
| 3 | cccc | ...... |
| 4 | dddd | ...... |
| 5 | eeee | ...... |
+-+——+———————————-+
5 rows in set (0.00 sec)

2つのテーブルの左結合の実行計画は次のとおりです。

mysql> desc select * from t2 left join t1 on t1.code = t2.code where t2.name = 'dddd'\G
******************* 1. row ****************
id: 1
select_type: SIMPLE
table: t2
partitions: NULL
type: ref
possible_keys: idx_name
key: idx_name
key_len: 83
ref: const
rows: 1
filtered: 100.00
Extra: NULL
****************** 2. row **************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 5
filtered: 100.00
Extra: Using where; Using join buffer (Block Nested Loop)
2 rows in set, 1 warning (0.01 sec)

t2.name = 'dddd'はインデックスを使用し、t1.code = t2.codeの関連条件はt1.codeのインデックスを使用しないことがはっきりとわかります。スコットも最初は戸惑いましたが、マシン嘘はつきません。スコットは、次のように書き換えられた実行プランを表示するためにshow警告を使用しました。

mysql> show warnings;

| Level | Code | Message |
| Note | 1003 | /* select#1 */ select `testdb`.`t2`.`id` AS `id`,`testdb`.`t2`.`name` AS `name`,`testdb`.`t2`.`code` AS `code`,`testdb`.`t1`.`id` AS `id`,`testdb`.`t1`.`name` AS `name`,`testdb`.`t1`.`code` AS `code` from `testdb`.`t2` left join `testdb`.`t1` on((convert(`testdb`.`t1`.`code` using utf8mb4) = `testdb`.`t2`.`code`)) where (`testdb`.`t2`.`name` = 'dddd') |

1 row in set (0.00 sec)

変換を発見した後(utf8mb4を使用したtestdb.t1.code)、スコットは2つのテーブルの文字セットが異なっていることを発見しました。t1はutf8、t2はutf8mb4です。しかし、なぜテーブル文字セットが異なる(実際にはフィールド文字セットが異なる)と、t1全テーブルスキャンが発生するのでしょうか。分析してみましょう。

  1. まず、t2左結合t1は、t2が駆動テーブルであることを決定します。このステップは、select * from t2 where t2.name = 'dddd'を実行し、コードフィールドの値を取り出すのと同じです。

  2. 次に、t2で見つかったコードの値を取得して、結合条件に従ってt1を検索します。この手順は、select * from t1 where t1.code = '8a77a32a7e0825f7c8634226105c42e5';を実行するのと同じです。

  3. ただし、ステップ(1)でt2テーブルから取得したコードフィールドはutf8mb4文字セットであり、t1テーブルのコードはutf8文字セットであるため、ここでは文字セット変換が必要であり、文字セット変換は小から大の原則に従っています。utf8mb4これはutf8のスーパーセットであるため、utf8をutf8mb4に変換します。つまり、t1.codeをutf8mb4文字セットに変換します。変換後、t1.codeのインデックスは依然としてutf8文字セットであるため、このインデックスは実行プランによって無視されます次に、t1テーブルは全テーブルスキャンのみを選択できます。さらに悪いことに、t2が複数のレコードをフィルターで除外すると、t1はテーブル全体によって複数回スキャンされ、パフォーマンスの違いが想像できます。

3.問題解決

原因が明らかになったので、それをどのように解決しますか?もちろん、文字セットは変更されています。t1をt2またはt2と同じになるように変更します。ここでは、t1をutf8mb4に変換することを選択します。文字セットをどのように変更しますか?

一部の学生は、alter table t1 charset utf8mb4を使用しますが、これは誤りです。これは、テーブルのデフォルトの文字セットを変更するためだけです。つまり、新しいフィールドはutf8mb4を使用し、既存のフィールドは引き続きutf8です。

mysql> alter table t1 charset utf8mb4;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> show create table t1\G
************** 1. row ***************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) CHARACTER SET utf8 DEFAULT NULL,
`code` varchar(50) CHARACTER SET utf8 DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

文字テーブルutf8mb4への変換テーブルt1変換のみを使用してください;正しいです。

ただし、alter tableの文字セットを変更する操作はブロックされて書き込まれるため(lock = nodeを使用するとエラーが報告されます)、ビジネスのピーク時には操作しないでください。ビジネスが少ない期間でも、大規模テーブルの操作はpt-onlineを使用することをお勧めします-schema-changeは、文字セットをオンラインで変更します。

mysql> alter table t1 convert to charset utf8mb4, lock=none;
ERROR 1846 (0A000): LOCK=NONE is not supported. Reason: Cannot change column type INPLACE. Try LOCK=SHARED.
mysql> alter table t1 convert to charset utf8mb4, lock=shared;
Query OK, 5 rows affected (0.04 sec)
Records: 5 Duplicates: 0 Warnings: 0

mysql> show create table t1\G
******************** 1. row **************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`code` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

実行計画をもう一度見てみると、問題がないことがわかります。

mysql> desc select * from t2 join t1 on t1.code = t2.code where t2.name = 'dddd'\G
******** 1. row ******************
id: 1
select_type: SIMPLE
table: t2
partitions: NULL
type: ref
possible_keys: idx_code,idx_name
key: idx_name
key_len: 83
ref: const
rows: 1
filtered: 100.00
Extra: Using where
********* 2. row *************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: ref
possible_keys: idx_code
key: idx_code
key_len: 203
ref: testdb.t2.code
rows: 1
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.00 sec)

4. 注意点

  1. テーブルの文字セットが異なる場合、結合SQLがインデックスを使用できなくなり、深刻なパフォーマンスの問題が発生する可能性があります。

  2. SQLがオンラインになる前に、SQL Reviewで適切な作業を行い、本番環境と同じ環境でレビューしてください。

  3. 文字セットを変更するテーブル操作を変更すると、書き込みがブロックされます。ビジネスの低いピークで操作するようにしてください。pt-online-schema-changeを使用することをお勧めします。

  4. テーブル構造の文字セットは一貫している必要があり、公開時にはレビュー作業を行う必要があります。

  5. テーブルの文字セットを大量に変更する場合は、SQL Reviewにも同じことを行い、関連するテーブルの文字セットも一緒に変更します。

5.ディスカッション

最後に、t1テーブルとt2テーブルの文字セットが変更されていないと仮定して質問をします。上記のSQLが次のように置き換えられた場合(つまり、t2左結合t1がt1左結合t2に置き換えられた場合)、インデックスエラーの問題はありますか?なんで?

select * from t1 join t2 on t1.code = t2.code where t1.name = 'dddd'
元の記事を136件公開 58のように 360,000以上を訪問

おすすめ

転載: blog.csdn.net/sunbocong/article/details/100558286
おすすめ