MySQL | さゆフィクション http://it.kensan.net/it aws wordpress などなどゆるーく書いてます Fri, 18 Aug 2023 20:13:58 +0000 ja hourly 1 https://wordpress.org/?v=6.4.3 https://it.kensan.net/wp-content/uploads/2023/03/cropped-icon-32x32.png MySQL | さゆフィクション http://it.kensan.net/it 32 32 MySQLのInnoDBとMyISAMのパフォーマンス比較をしてみました https://it.kensan.net/mysql-innodb-myisam-performance.html Fri, 18 Aug 2023 20:05:08 +0000 https://it.kensan.net/?p=1710 MySQLのInnoDBとMyISAMのパフォーマンス比較をしてみましたー

対象のMySQLのバージョンは5.7と8.0です。

結論としては、

  • INSERTはMyISAMの方が早い
  • SELECT・UPDATE・DELETEは
    • 8.0ではInnoDBの方が早い
    • 5.7ではSELECT・DELETEはMyISAMの方が早い。UPDATEはInnoDBの方が早い

という結果でしたー!

なお、MySQLのバージョンごとのサポート期限は以下のようになっています。

MySQLバージョン MySQL AWS RDSのMySQL AWS AuroraのMySQL
5.7 2023年10月31日 2023 年 10 月 2024 年 10 月 31 日

(Aurora バージョン2)

8.0 2026年4月30日 未定 未定

(Aurora バージョン3)

検証条件

  • MySQL5.7-MyISAMで、1万件のデータ登録をした後、SELECT・UPDATE・DELETE
  • MySQL5.7-InnoDBで、1万件のデータ登録をした後、SELECT・UPDATE・DELETE
  • MySQL8.0-MyISAMで、1万件のデータ登録をした後、SELECT・UPDATE・DELETE
  • MySQL8.0-InnoDBで、1万件のデータ登録をした後、SELECT・UPDATE・DELETE

比較結果詳細

MySQL5.7 MySQL8.0
MyISAM InnoDB MyISAM InnoDB
1万件データ作成

24.96 sec

53.24 sec

21.39 sec

48.92 sec

1万件カウント

0.00016000 sec

0.00359175 sec

0.00018900 sec

0.00850275 sec

1SELECT

0.00127125 sec

0.00401250 sec

0.07204400 sec

0.00575250 sec

1UPDATE

0.06670350 sec

0.01640200 sec

0.02672875 sec

0.01512700 sec

1DELETE

0.00380375 sec

0.00935400 sec

0.02560125 sec

0.01409000 sec

準備

検証環境:AWS RDS
インスタンスクラス:db.t3.micro


-- 区切り文字を「//」に変更する
DELIMITER //

-- make_sample_dataというテストデータ作成用プロシージャーを作成する

create procedure make_sample_data(in i int)
begin
  declare count int default 0;
  -- 繰り返し
  while count < i do
    set count = count + 1;
    INSERT INTO user VALUES(count, MOD(count,2),CONCAT('usr_name_',count));
  end while;
end
//
-- 区切り文字を「;」に戻す
DELIMITER ;

MySQL5.7 MyISAM

create table user(
id INT,
type INT,
name VARCHAR(255)
) engine MyISAM;

 

mysql> select version();
+------------+
| version()  |
+------------+
| 5.7.41-log |
+------------+
1 row in set (0.00 sec)

◾️1万件データ作成

call make_sample_data(10000);
Query OK, 1 row affected (24.96 sec)

◾️1万件カウント

select count(*) from user;
+----------+
| count(*) |
+----------+
|    10000 |
+----------+
0.00016000 

◾️1件SELECT

select * from user where id = 5000;
+------+------+---------------+
| id   | type | name          |
+------+------+---------------+
| 5000 |    0 | usr_name_5000 |
+------+------+---------------+
0.00127125 

◾️1件UPDATE

update user set name = 'name_5001' where id = 5001;
0.06670350 

◾️1件DELETE

delete from user where id = 5002;
0.00380375 

MySQL5.7 InnoDB

create table user(
id INT,
type INT,
name VARCHAR(255)
) engine InnoDB;

◾️1万件データ作成

call make_sample_data(10000);
Query OK, 1 row affected (53.24 sec)

◾️1万件カウント

select count(*) from user;
+----------+
| count(*) |
+----------+
|    10000 |
+----------+
0.00359175 

◾️1件SELECT

select * from user where id = 5000;
+------+------+---------------+
| id   | type | name          |
+------+------+---------------+
| 5000 |    0 | usr_name_5000 |
+------+------+---------------+
0.00401250

◾️1件UPDATE

update user set name = 'name_500001' where id = 5001;
0.01640200 

◾️1件DELETE

delete from user where id = 5002;
0.00935400 

MySQL8.0 MyISAM

create table user(
id INT,
type INT,
name VARCHAR(255)
) engine MyISAM;
select version();
+-----------+
| version() |
+-----------+
| 8.0.32    |
+-----------+

◾️1万件データ作成

call make_sample_data(10000);
Query OK, 1 row affected (21.39 sec)

◾️1万件カウント

select count(*) from user;
+----------+
| count(*) |
+----------+
|    10000 |
+----------+
0.00018900 

◾️1件SELECT

select * from user where id = 5000;

+------+------+---------------+
| id   | type | name          |
+------+------+---------------+
| 5000 |    0 | usr_name_5000 |
+------+------+---------------+
0.07204400 

◾️1件UPDATE

update user set name = 'name_500001' where id = 5001;
0.02672875 

◾️1件DELETE

delete from user where id = 5002;
0.02560125

MySQL8.0 InnoDB

create table user(
id INT,
type INT,
name VARCHAR(255)
) engine InnoDB;

◾️1万件データ作成

call make_sample_data(10000);
Query OK, 1 row affected (48.92 sec)

◾️1万件カウント

select count(*) from user;
+----------+
| count(*) |
+----------+
|    10000 |
+----------+
0.00850275

◾️1件SELECT

select * from user where id = 5000;
+------+------+---------------+
| id   | type | name          |
+------+------+---------------+
| 5000 |    0 | usr_name_5000 |
+------+------+---------------+
0.00575250 

◾️1件UPDATE

update user set name = 'name_500001' where id = 5001;
0.01512700

◾️1件DELETE

delete from user where id = 5002;
0.01409000 

まとめ

MySQLのInnoDBとMyISAMのパフォーマンス比較をしてみましたー

結論としては、

  • INSERTはMyISAMの方が早い
  • SELECT・UPDATE・DELETEは
    • 8.0ではInnoDBの方が早い
    • 5.7ではSELECT・DELETEはMyISAMの方が早い。UPDATEはInnoDBの方が早い

という結果でしたー!

MyISAMとInnoDBの違いを比較した記事もありますので、よろしければ、ご参照くださいー

MySQL8.0でのMyISAMとInnoDBの違いを比較
MySQL8.0でのストレージエンジンのMyISAMとInnoDBの違いについて記載しています。 InnoDBには、トランザクション機能や外部キー機能がありますので、 MyISAMとInnoDBで迷ったら、多くの主要な機能が備わっているInnoDBを選択しましょ!
]]>
AWS Aurora Serverless v2の用途や注意点 https://it.kensan.net/aws-aurora-serverless-v2.html Sat, 06 May 2023 02:47:37 +0000 https://it.kensan.net/?p=1626 Aurora Serverless v2について記載します。

具体的には以下のことについて記載していきます。

  • Aurora Serverless v2とは
  • 用途
  • 注意点

まずはAurora Serverless v2とは?について書いていきます。

Aurora Serverless v2とは

Aurora Serverless v2は、オンデマンドでAuto Scaling設定できるデータベースです。

以下の点について、具体的に記載していきます。

これから記載すること

性能
スケーリング
可用性

性能

ACUという単位で管理されることになります。

1ACUは、約2GB のメモリと、対応する CPU、ネットワークの組み合わせとなります。

ACUは最大、128 ACU を指定することができます。

ACUの最大はどれくらいのインスタンスかというと、メモリは128 ACU × 2GB = 256GBとなります。DBインスタンスクラスタイプがr6gの場合、以下の公式ページから、r6gかつメモリ:256GBのインスタンスを探して、db.r6g.8xlargeが最大ということになります。

Aurora DB インスタンスクラス - Amazon Aurora
DB インスタンスクラスによって、 Amazon Aurora DB インスタンスの計算とメモリの容量を決定します。
ACUの最大の時の性能例

インスタンスクラス:db.r6g.8xlarge
vCPU:32
メモリ(GB):256

 

スケーリング

Aurora Serverless v2は以下に記載されている通り、スケーリングが早く、アクセスのスパイクにも耐えたれそうです。(実際に耐えられるかどうかは、システムがDBへどれくらいの負荷をかけるか次第になるので、要検証ですね)

Amazon Aurora Serverless v2 は、ほんの一瞬で数十万ものトランザクションにスケールできます。スケールに応じて、容量をきめ細かい増分で調整し、アプリケーションが必要とする適切な量のデータベースリソースを提供します。

Amazon Aurora Serverless | AWS
Amazon Aurora Serverless では、管理する DB インスタンスはありません。データベースは、アプリケーションニーズに応じて、容量を自動的に起動、停止、および拡大または縮小します。

また、Aurora Serverless v2 では、以下のようなケースでもスケーリング可能なようです。

  • データベースの接続中
  • SQL トランザクション処理中
  • テーブルロック中
  • 一時テーブルの使用中

可用性

Aurora Serverless v2は、以下の特徴を持ち、高可用性を確立しています。

  • マルチ AZ 対応
  • 自動的フェイルオーバー

次は、用途について記載します。

用途

  • 変化の大きいアクセスやアクセス量が予測できない時
    アクセスが多い時と少ない時の落差が大きい場合や、アクセス量が予測できない場合、データベースは負荷に応じて、自動的にスケーリングしてくれます。
  • 開発時のAuroraの代替
    本番は、Auroraを使用している場合でも、開発環境は、Aurora Serverlessを利用するとコスト削減につながることあります。

次は、注意点です。

 

注意点

以下に注意点を記載します。

  • MySQLの場合、Aurora MySQL バージョン 3のみ対応しています
    • MySQLのバージョン8系しか対応していないということになります
  • ACUは最大、128 ACU(db.r6g.8xlargeくらい)が最大となっています
    • 最大が決まっているため、要注意です
  • Aurora Serverless v1 では Data API が使えましたが、v2ではData APIは使えません。
  • 費用の予測がしにくい
    • 最大ACUから最大の費用は算出することができますが、実際にどれくらいの費用となるかについては予測がしづらいです

まとめ

v1では、SQL実行中はスケーリングできないなどの制限がありましたが、v2では改善され用途が広がりそうですね。素晴らしーい!

]]>
MySQLでSELECT FROMの中でSELECTしてみる https://it.kensan.net/mysql_subquery.html Thu, 04 May 2023 04:03:51 +0000 https://it.kensan.net/?p=1624 MySQLでSELECT FROMの中でSELECTしてみるというサブクエリの話です。

具体的には以下のように、SELECTのFROM句の中でのSELECTについて書いていきます。

SELECT {取得したいカラム}
FROM
    (
        SELECT {取得したいカラム}
        FROM {テーブル名}

    ) AS {サブクエリ名}

SELECTのFROM句の中でのSELECTはサブクエリと言います!

SELECTのFROM句以外でもサブクエリは使えますが、今回はSELECTのFROM句に絞ってのサブクエリについて記載します。

試しにサブクエリしてみます!

SELECT FROMの中でSELECTしてみる

まずテーブルを作成します。

create table user(
id INT,
type INT,
name VARCHAR(255)
);

1件INSERTします。

INSERT INTO user VALUES(1, 2,'usr_name_1');

SELECTのFROM句の中でSELECTしてみます。

SELECT
    subquery.id
FROM
    (
        SELECT
            id
        FROM
            user
    ) AS subquery
;

実行結果は以下のようになります。

+------+
| id   |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

SELECT FROMの中でSELECTする際のポイント

ポイント

FROM句の中で(SELECT文)を使用する
(SELECT文) AS でサブクエリ名を付ける
(SELECT文) で取得したカラムについて、外側のSELECT文で取得可能
→サブクエリ名.カラム名で取得できる(上記の例だと「subquery.id」)

上記の例では、「SELECT id FROM user」と実行結果が同じになり、「SELECT FROMの中でSELECTする」意味がない(SQLが複雑になるだけ)です。

次は、実際に「SELECT FROMの中でSELECTする」と効果がある例について記載します。

以降、「SELECT FROMの中でSELECTする」はサブクエリと呼びます。

サブクエリの効果的な使い方

サブクエリを使わないと取得が難しい場合や、サブクエリを使うことで高速化が測れる場合などで使用します。サブクエリ内でデータの絞り込みが行える場合、使用するとクエリの高速化ができます。

サブクエリを使用するケース

サブクエリを使わないと取得が難しい場合
サブクエリを使うことで高速化が測れる場合
→サブクエリ内でデータの絞り込みが行える場合、使用するとクエリの高速化ができるケースがあります。

具体的にクエリ例を記載していきます!

こんなときにサブクエリが使えるよ!という具体例を記載していきます。

まずは、準備です。

準備

◾️テーブル作成

以下のテーブルを作成します。

create table user(
id INT AUTO_INCREMENT,
type INT,
name VARCHAR(255),
PRIMARY KEY (id)
);

create table user_score(
id INT AUTO_INCREMENT,
user_id INT,
score INT,
PRIMARY KEY (id), index(user_id)
);

◾️プロシージャ作成

次は、テストデータはプロシージャーで作成しますのでプロシージャを作成します。

以下の通りデータ作成してくれるようになっています。

  • user.type:カウントを2で割った余りを格納(あまり意味がないカラムです。)
  • user.name:「user_name_{カウント}」の値を格納
  • user.score:ランダムな数値を格納
-- 区切り文字を「//」に変更する
DELIMITER //

-- make_sample_dataというテストデータ作成用プロシージャーを作成する

create procedure make_sample_data(in i int)
begin
  declare count int default 0;
  -- 繰り返し
  while count < i do
    set count = count + 1;
    INSERT INTO user (type, name) VALUES(MOD(count,2),CONCAT('usr_name_',count));
    INSERT INTO user_score (user_id, score) VALUES(count, CEIL(RAND() * 1000));
    INSERT INTO user_score (user_id, score) VALUES(count, CEIL(RAND() * 1000));
    INSERT INTO user_score (user_id, score) VALUES(count, CEIL(RAND() * 1000));
  end while;
end
//
-- 区切り文字を「;」に戻す
DELIMITER ;

◾️100万件インサート

mysql> call make_sample_data(1000000);
Query OK, 1 row affected (4 min 29.74 sec)

◾️バージョン確認

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.26    |
+-----------+
1 row in set (0.02 sec)

準備ができたので、サブクエリを使っていきます!

まずは、サブクエリを使わないと取得が難しい場合の例です。

サブクエリを使わないと取得が難しい場合

最も大きいデータや最も小さいデータの取得など、集計した結果の最大値、最小値を求める際には、サブクエリを使うと便利です。

例えば、上記で準備したテーブルのスコアが最も大きいユーザのスコアを取得するなどです。

スコアが最も大きいユーザのスコアを取得するサブクエリSQLは以下となります。

select max(score_sum.sum) as max from (
        SELECT
            `user_id`,
           SUM(`score`) AS `sum`
        FROM
            `user_score`
        GROUP BY
            `user_id`
    ) AS `score_sum`
;
// 実行結果
+------+
| max  |
+------+
| 2998 |
+------+
1 row in set (10.51 sec)

サブクエリを使うことで高速化が測れる場合

サブクエリ内でデータの絞り込みが行える場合、使用するとクエリの高速化ができます。

上記で準備したテーブルで、ユーザのスコアの平均が990のデータを絞り込む例で考えてみます。

◾️サブクエリを取得した場合

SELECT
    `user`.*,
    `score`.`average`
FROM
    (
        SELECT
            `user_id`,
            AVG(`score`) AS `average`
        FROM
            `user_score`
        GROUP BY
            `user_id`
        HAVING `average` > 990
    ) AS `score`
    JOIN
        `user`
    ON  `user`.`id` = `score`.`user_id`
;
// 実行結果
59 rows in set (8.75 sec)

◾️JOINを使用した場合

SELECT
    `user`.*,
    AVG(`user_score`.`score`) AS `average`
FROM
    `user`
    LEFT JOIN
        `user_score`
    ON  `user`.`id` = `user_score`.`user_id`
GROUP BY
    `user_id`
HAVING `average` > 990
;
// 実行結果
59 rows in set (17.68 sec)

サブクエリの方が高速でしたー

まとめ

サブクエリについて記載しましたー

まとめ

SELECTのFROM句の中でのSELECT
→サブクエリと言います!
<サブクエリを使うケース>
サブクエリを使わないと取得が難しい場合
サブクエリを使うことで高速化が測れる場合
→サブクエリ内でデータの絞り込みが行える場合、使用するとクエリの高速化ができるケースがあります。

]]>
MySQLのInnoDBファイルフォーマットのAntelopeとBarracudaについて https://it.kensan.net/mysql_innodb_file_format.html Wed, 03 May 2023 04:39:57 +0000 https://it.kensan.net/?p=1588 MySQLのInnoDBのファイルフォーマットのAntelopeとBarracudaについて記載します。

MySQL5.6以前はAntelopeがデフォルトのファイルフォーマットで、5.7以降はBarracudaがデフォルトのファイルフォーマットになります。

ファイルフォーマットのデフォルトがAntelopeからBarracudaになったことで、変わったことを書いていきます。

変わったこと

ファイルフォーマットが変更になったことで、行フォーマットも変更になりました
→これにより、行サイズ制限が変わってきます

ファイルフォーマットが変更になったことで、行フォーマットが変更になりましたので、行フォーマットについて記載します!

行フォーマットについて

AntelopeではCompactがデフォルトの行フォーマットで、BarracudaではDynamicがデフォルトの行フォーマットになります。

  • COMPACT(Antelopeのデフォルト)
    •  BLOB および TEXT のカラムは、先頭768バイトだけレコードに格納され、残りは外部のページに格納される。
  • DYNAMIC(Barracudaのデフォルト)
    • BLOB および TEXT のカラムは、全てオフページに格納でき、ページへのポインタのみレコードに格納される

以外にも以下の2つの行フォーマットがあります。

  • REDUNDANT
  • COMPRESSED

行フォーマットの詳細は公式ページをご参照ください。

https://dev.mysql.com/doc/refman/8.0/ja/innodb-row-format.html

行フォーマットの変更により、行サイズ制限への影響があります!

行サイズ制限への影響

まず、InnoDBには行サイズ制限があり、 1レコードあたり65,535 バイトまで格納可能となっています。

fw_error_www
fw_error_www

行フォーマットが変更になってことにより、1レコードあたり65,535 バイトという制限の計算方法が異なります。

  • COMPACT(Antelopeのデフォルト)
    • BLOB および TEXT のカラムは先頭768バイトがレコードに格納される
      • 行サイズ制限に最大768 バイト影響する(先頭の768バイトなので、768バイトより小さい入力の場合は、768バイトより小さくなる)
        • レコード登録時に、行サイズ制限に引っかかった場合にエラーとなる
  • DYNAMIC(Barracudaのデフォルト)
    • BLOB および TEXT のカラムは、全てオフページに格納でき、ページへのポインタのみレコードに格納される
      • 行サイズ制限に 9 から 12 バイトのみ影響する

ファイルフォーマットと行フォーマットの確認方法

以下のコマンドでファイルフォーマットを確認できます。

SHOW GLOBAL VARIABLES LIKE 'innodb_file_format';

以下のコマンドで行フォーマットを確認可能です。

SHOW TABLE STATUS LIKE 'テーブル名';

まとめ

ファイルフォーマットのデフォルトがAntelopeからBarracudaになったことで、変わったことと、ファイルフォーマットの確認方法を記載しました。

まとめ

ファイルフォーマットが変更になったことで、行フォーマットも変更になりました
→これにより、行サイズ制限が変わってきます

ファイルフォーマットと行フォーマット難しい…!

]]>
MySQLのテーブル定義書をHTML形式でリバースエンジニアリングする https://it.kensan.net/mysqldb_table_define.html Wed, 03 May 2023 01:14:05 +0000 https://it.kensan.net/?p=1621 MySQLのテーブル定義書をリバースエンジニアリングで簡単に作成する方法について記載します。

MySQLのテーブル定義書を簡単に作りたいよ〜という方向けの記事です!

MySQLWorkbenchをリバースエンジニアリングして、テーブル定義書やER図もあるかと思いますが、今回はMySQLWorkbenchを使いません。

簡単なコマンド実行だけでHTML形式のテーブル定義書が作成できます。

やること

テーブル定義書のリバースエンジニアリング
HTLM形式のテーブル定義書を作成

作成されるテーブル定義書の完成イメージは以下となります。

HTML形式のテーブル定義書

HTML形式のテーブル定義書

 

早速、HTML形式でテーブル定義書をリバースエンジニアリングしてみます!

リバースエンジニアリングしてみる

以下の3ステップでテーブル定義書をHTML形式で作成できます。

テーブル定義書作成の3ステップ

DDLをXML形式でダンプ出力
スタイル作成
HTML変換

DDLをXML形式でダンプ出力

mysqldumpコマンドを使ってDDL(テーブル構造情報)をDBから出力します。

具体的には以下のコマンドになります。

mysqldump --no-data --xml -u {ユーザ名} -p {DB名} > {出力ファイル名}

ユーザ名:mysqlに入る際のユーザ名を入力

DB名:テーブル定義を作成したいDB名を入力

出力ファイル名:出力時のファイル名を指定します。拡張子はxmlとします。

以下、「ユーザ名:root  DB名:test  出力ファイル名:filename.xml」でxmlファイル出力時のコマンド例です。

mysqldump --no-data --xml -u root -p test > filename.xml

スタイル作成

以下のサイトで公開されている、スタイルを参考にさせていただくと良いです。
MySQL テーブル仕様書メーカー的な何か

上記サイトにアクセスして、右側にXSLT スタイルシートのボックスがありますので、このボックスの中身をコピーさせていただき、style.xsl というファイル名で保存します。

style.xsl は自分で作成したものを使っても問題ありません。

これでスタイルの作成は完了です。

HTML変換

以下のコマンドでHTMLに変換します。

xsltproc -o {HTMLファイル名} {スタイルファイル名} {XMLファイル名}

HTMLファイル名:出力時のファイル名を指定します。拡張子はhtmlとします。

スタイルファイル名:スタイル作成で作成したスタイルファイルを指定します。

XMLファイル名:DDLをXML形式でダンプ出力で出力したファイルを指定します。

以下、「HTMLファイル名:test.html  スタイルファイル名:style.xsl XMLファイル名:test.xml」でxmlファイル出力時のコマンド例です。

xsltproc -o test.html style.xsl test.xml

xsltprocとは

xsltprocは、XSLTスタイルシートを使って、XMLを他のフォーマットに変換するコマンドです。

以下の形式で実行することでフォーマットを変換できます。

xsltproc -o 出力ファイル名 XSLファイルパス XMLファイルパス

これでHTML形式のテーブル定義書が出来上がりました!

次は、出来上がったテーブル定義書を確認します。

作成したテーブル定義書を確認する

WEBブラウザでテーブル定義書を開くことで、作成したテーブル定義書を確認できます。

以下のようなテーブル定義書が確認できます!

HTML形式のテーブル定義書

HTML形式のテーブル定義書

まとめ

以下のことをやってみました。

やること

<やったこと>
テーブル定義書のリバースエンジニアリング
HTLM形式のテーブル定義書を作成
<方法>
DDLをXML形式でダンプ出力
スタイル作成
HTML変換

簡単にテーブル定義が作成できました。個人的には、mysqlダンプコマンドでxml形式のダンプができることに少し驚きでしたー

]]>
MySQL8.0でのMyISAMとInnoDBの違いを比較 https://it.kensan.net/mysql8_myisam_innodb.html Tue, 02 May 2023 14:57:03 +0000 https://it.kensan.net/?p=1590 MySQL8.0でのストレージエンジンのMyISAMとInnoDBの違いについて書いていきます。

まず公式ページの記載は以下となっています。

MyISAM

fw_error_www
fw_error_www

InnoDB

fw_error_www
fw_error_www

公式ページを見つつ、MyISAMとInnoDBの違いについて記載していきます。

MySQL8.0でのMyISAMとInnoDBの違いについて、結論は以下の通りとなります。

MyISAMとInnoDBの違い

<MyISAM>
トランザクション機能なし
ロックの粒度:テーブル
外部キー機能なし
<InnoDB>
トランザクション機能あり
ロックの粒度:行
外部キー機能あり

MyISAMとInnoDBで迷ったら、多くの主要な機能が備わっているInnoDBを選択しましょ!

MyISAMとInnoDBの違いについて詳細を記載する前に、MyISAMとInnoDBの歴史について書いていきます!

MyISAMとInnoDBの歴史

以下のポイントに絞って、MyISAMとInnoDBの歴史をたどっていきます!

ポイント

デフォルトストレージエンジン
全文検索インデックスのサポート
→MySQL 5.7からInnoDBでもサポート
地理空間インデックスのサポート

→MySQL 5.6からInnoDBでもサポート

デフォルトストレージエンジン

 MySQL バージョン5.5より前のバージョンでは、MyISAMがデフォルトストレージエンジンでしたが、バージョン5.5からInnoDBがデフォルトストレージエンジンとなりました

デフォルトストレージエンジンとは、以下のようにストレージエンジンを指定せずに、テーブル作成した際に選択されるストレージエンジンを指しています。

create table user(
id INT,
type INT,
name VARCHAR(255)
);

上記のSQLでテーブルを作成すると、MySQL5.5以降ではデフォルトストレージエンジンのInnoDBでテーブルが作成されます。 MySQL バージョン5.5より前のバージョンでは、MyISAMでテーブルが作成されます。

ストレージエンジンを指定したい場合は、テーブル作成時にストレージエンジンを以下のように明記します。

MyISAMを指定してテーブル作成時

create table user(
id INT,
type INT,
name VARCHAR(255)
) engine MyISAM;

<InnoDBを指定してテーブル作成時>

create table user(
id INT,
type INT,
name VARCHAR(255)
) engine InnoDB;

全文検索インデックスのサポート

全文検索インデックスは、MyISAMがサポートしている機能でしたが、MySQL 5.7からInnoDBでもサポートされるようになりました。

MySQL 5.7より前の時代では、全文検索したい場合は、MyISAMを選択肢として挙げられていたかと思いますが、MySQL 5.7から全文検索したい場合もInnoDBでOKということになります。

地理空間インデックスのサポート

地理空間インデックスもMyISAMがサポートしている機能でしたが、MySQL 5.6からInnoDBでもサポートされるようになりました。

MySQL 5.6より前の時代では、地理空間インデックスを使いたい場合は、MyISAMを選択肢としていたかと思いますが、MySQL 5.6からはInnoDBでOKということになります。

地理空間については以下の公式ページをご参照いただければと!

fw_error_www
fw_error_www

MyISAMとInnoDBの歴史をたどってきました。

MyISAMにあってInnoDBに無かった機能が、InnoDBでもサポートされるようになり、InnoDBの強化が行われてきたことが分かると思います。

 

次に、MySQL8.0でのMyISAMとInnoDBの違いについて詳細を記載していきます!

MySQL8.0でのMyISAMとInnoDBの違い

以下の点について、記載していきます。

MyISAMとInnoDBの違い

<MyISAM>
トランザクション機能なし
ロックの粒度:テーブル
外部キー機能なし
<InnoDB>
トランザクション機能あり
ロックの粒度:行
外部キー機能あり

トランザクション機能

トランザクション機能は、InnoDBにあって、MyISAMにはない機能になります。

トランザクション機能を活用することで、中途半端な状態でデータが登録・更新されることを防ぐことができます。

トランザクションについて、例えば、テーブルAとテーブルBを一度に更新したいケースでは、トランザクションを使うと、テーブルAを更新してテーブルBの更新に失敗した場合、テーブルAの更新も無かったことにできる(ロールバックできる)機能を指しています。

これにより、データの整合性が保ちやすくなります。

ロックの粒度

InnoDBでは行、MyISAMではテーブル単位でのロックとなります。

テーブル単位でのロックですと、テーブルAに対する更新処理を二人で同時に行った場合、最初の一人の処理が終わるまで、二人目の処理は待たされることになります。

一方、行単位でのロックですと、テーブルAに対する更新処理を二人で同時に行った場合でも、更新するレコードが異なれば、最初の一人の処理と並行して、二人目の処理も実行されることになります。

行単位のロックの方が、ロック解除待ちが発生する可能性が少ないということになります。

外部キー機能

外部キー機能は、InnoDBにあって、MyISAMにはない機能になります。

外部キー機能を使って、テーブル同士の紐づけに用いるカラム指定することができます。

例えば、親子関係のあるテーブルに外部キーを貼ると、親テーブルのみデータ削除して、削除したデータに紐づく小テーブルのデータは削除しないといったことができなくなる制約が発生します。これにより、データの整合性を保ちやすくなります。

まとめ

MySQL8.0でのMyISAMとInnoDBの違いについて、まとめると以下のようになると思います。

MyISAMとInnoDBの違い

<MyISAM>
トランザクション機能なし
ロックの粒度:テーブル
外部キー機能なし
<InnoDB>
トランザクション機能あり
→メリット:データの整合性を保ちやすくなる
ロックの粒度:行
→メリット:ロック待ちの頻度が下がる
外部キー機能あり
→メリット:データの整合性を保ちやすくなる

昔はInnoDBでは使えなかった「全文検索インデックス」や「地理空間インデックス」もInnoDBで使えるようになり、InnoDB素晴らしーいという結論です。

]]>
MySQLで2回目のSELECTで速度が向上するクエリキャッシュについて https://it.kensan.net/mysql-query-cache.html Tue, 02 May 2023 12:56:51 +0000 https://it.kensan.net/?p=1586 MySQLで同じSELECT文の2回目以降の読み取り速度を向上させるクエリキャッシュですが、MySQL 8.0ではキャッシュしません!ということについて書いていきます。

MySQL5.7までは、デフォルトでONになっていたクエリキャッシュですが、MySQL8.0では使えない機能(削除された機能)となります。

MySQL5.7→8.0に移行時は要注意ですね。

The query cache is deprecated as of MySQL 5.7.20, and is removed in MySQL 8.0.

fw_error_www
fw_error_www
クエリキャッシュのポイント

MySQL5.7まではデフォルトON
→これにより、同じSELECT文の2回目以降の読み取り
速度が向上していた
MySQL8.0では使えない

まずは、具体的にクエリキャッシュの挙動について書いていきます!

クエリキャッシュの挙動

クエリキャッシュの挙動は以下のようになります。

  • SELECT文の結果をメモリに保存する
    • 同じSELECT文を発行すると、2回目以降はメモリ上の保存データ(キャシュデータ)が返される
  • UPDATEするとメモリ上の保存データ(キャッシュデータ)はクリアされる
    • これにより、UPDATE前の古いデータが、SELECTの結果として返却されることはない

次は、クエリキャッシュON(MySQL5.7)とクエリキャッシュOFF(MySQL8.0)で実際の挙動を確認していきます。

クエリキャッシュの動作検証

環境:AWS Aurora

インスタンス:db.t3.medium

動作検証内容

クエリキャッシュON(MySQL5.7)とクエリキャッシュOFF(MySQL8.0)で、それぞれ以下の挙動を確認します。
同じSELECT文を3回実行した際の速度検証
UPDATEでデータ更新後に、再度、同じSELECT文を3回実行した際の速度検証

 

準備

以下の2つを準備します。

2つの準備

テーブル作成
テストデータ作成用のプロシージャ作成

テーブル作成

以下のテーブルを用意します。インデックスなしテーブルです。

create table user(
id INT,
type INT,
name VARCHAR(255)
);

テストデータ作成用のプロシージャ作成

テストデータはプロシージャーで作成します。

以下の通りデータ作成してくれるようになっています。

  • user.id:カウントアップ形式で数値を格納
  • user.type:カウントを2で割った余りを格納(あまり意味がないカラムです。)
  • user.name:「user_name_{カウント}」の値を格納

-- 区切り文字を「//」に変更する
DELIMITER //

-- make_sample_dataというテストデータ作成用プロシージャーを作成する

create procedure make_sample_data(in i int)
begin
  declare count int default 0;
  -- 繰り返し
  while count < i do
    set count = count + 1;
    INSERT INTO user VALUES(count, MOD(count,2),CONCAT('usr_name_',count));
  end while;
end
//
-- 区切り文字を「;」に戻す
DELIMITER ;

それでは、クエリキャッシュON(MySQL5.7)の挙動から確認していきます。

クエリキャッシュON(MySQL5.7)の挙動

◾️クエリキャッシュがONであることの確認

mysql> SHOW VARIABLES LIKE 'have_query_cache';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| have_query_cache | YES   |
+------------------+-------+
1 row in set (0.01 sec)

◾️100万件インサート


mysql> call make_sample_data(1000000);
Query OK, 1 row affected (48.14 sec)

◾️1件SELECTを3回やってみる

<結果>

1回目:0.47 sec→2回目:0.02 sec→3回目:0.01 sec

2回目以降はキャッシュにより、大幅に速度向上していることが分かる


mysql> select * from user where id = 1;
+------+------+------------+
| id   | type | name       |
+------+------+------------+
|    1 |    1 | usr_name_1 |
+------+------+------------+
1 row in set (0.47 sec)

mysql> select * from user where id = 1;
+------+------+------------+
| id   | type | name       |
+------+------+------------+
|    1 |    1 | usr_name_1 |
+------+------+------------+
1 row in set (0.02 sec)

mysql> select * from user where id = 1;
+------+------+------------+
| id   | type | name       |
+------+------+------------+
|    1 |    1 | usr_name_1 |
+------+------+------------+
1 row in set (0.01 sec)

◾️SELECT対象データをUPDATEする


mysql> update user set name = 'update_name' where id = 1;
Query OK, 0 rows affected (0.72 sec)
Rows matched: 1  Changed: 0  Warnings: 0

◾️UPDATE後に再度、1件SELECTを3回やってみる

<結果>

1回目:0.45 sec→2回目:0.00 sec→3回目:0.00 sec

UPDATEによりキャッシュがクリアされ、1回目は遅くなり、2回目以降はキャッシュにより、大幅に速度向上していることが分かる


mysql> select * from user where id = 1;
+------+------+-------------+
| id   | type | name        |
+------+------+-------------+
|    1 |    1 | update_name |
+------+------+-------------+
1 row in set (0.45 sec)

mysql> select * from user where id = 1;
+------+------+-------------+
| id   | type | name        |
+------+------+-------------+
|    1 |    1 | update_name |
+------+------+-------------+
1 row in set (0.00 sec)

mysql> select * from user where id = 1;
+------+------+-------------+
| id   | type | name        |
+------+------+-------------+
|    1 |    1 | update_name |
+------+------+-------------+
1 row in set (0.00 sec)

次に、クエリキャッシュOFF(MySQL8.0)の挙動を確認していきます。

クエリキャッシュOFF(MySQL8.0)の挙動

◾️クエリキャッシュがOFFであることの確認

mysql> SHOW VARIABLES LIKE 'have_query_cache';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| have_query_cache | NO    |
+------------------+-------+
1 row in set (0.00 sec)

◾️100万件インサート

mysql> call make_sample_data(1000000);
Query OK, 1 row affected (47.50 sec)

◾️1件SELECTを3回やってみる

<結果>

1回目:0.57 sec→2回目:0.55 sec→3回目:0.58 sec

2回目以降も速度はほとんど変わらず、キャッシュが効いていないことが分かる。

mysql> select * from user where id = 1;
+------+------+------------+
| id   | type | name       |
+------+------+------------+
|    1 |    1 | usr_name_1 |
+------+------+------------+
1 row in set (0.57 sec)

mysql> select * from user where id = 1;
+------+------+------------+
| id   | type | name       |
+------+------+------------+
|    1 |    1 | usr_name_1 |
+------+------+------------+
1 row in set (0.55 sec)

mysql> select * from user where id = 1;
+------+------+------------+
| id   | type | name       |
+------+------+------------+
|    1 |    1 | usr_name_1 |
+------+------+------------+
1 row in set (0.58 sec)

◾️SELECT対象データをUPDATEする

mysql> update user set name = 'update_name' where id = 1;
Query OK, 1 row affected (0.90 sec)
Rows matched: 1  Changed: 1  Warnings: 0

◾️UPDATE後に再度、1件SELECTを3回やってみる

<結果>

1回目:0.55 sec→2回目:0.54 sec→3回目:0.54 sec

UPDATE後も変わらず、キャッシュが効かないため、速度が変わらないことが分かる

mysql> select * from user where id = 1;
+------+------+-------------+
| id   | type | name        |
+------+------+-------------+
|    1 |    1 | update_name |
+------+------+-------------+
1 row in set (0.55 sec)

mysql> select * from user where id = 1;
+------+------+-------------+
| id   | type | name        |
+------+------+-------------+
|    1 |    1 | update_name |
+------+------+-------------+
1 row in set (0.54 sec)

mysql> select * from user where id = 1;
+------+------+-------------+
| id   | type | name        |
+------+------+-------------+
|    1 |    1 | update_name |
+------+------+-------------+
1 row in set (0.54 sec)

 

まとめ

クエリキャッシュの挙動について、動作検証しましたー!

以下のことがわかったかと思いまーす!

クエリキャッシュ挙動まとめ

MySQL5.7まではデフォルトON
→同じSELECT文の2回目以降の読み取りの速度が向上していた
→UPDATE時にキャッシュがクリアされるため、UPDATE後の1回目のSELECTはキャッシュが効かず、2回目以降キャッシュが効く
MySQL8.0では使えない

]]>
MySQLのクエリ実行速度をミリ秒単位で計測する https://it.kensan.net/mysql_exec_measurement.html Sun, 30 Apr 2023 20:40:48 +0000 https://it.kensan.net/?p=1572 MySQLのクエリ実行速度をミリ秒単位で計測する方法を記載します。

ターミナルでMySQLサーバに入って、SQLを実行すると以下のように秒単位での速度は計測できますが、もう少し詳細に計測したい(ミリ秒単位で計測したい)、という場合の対処法です。

mysql> select * from it_users ;
// セレクト結果
1 row in set (0.00 sec)

それでは、早速結論です。

結論:SHOW PROFILESを使用します

本記事で記載すること

MySQLのクエリ実行速度をミリ秒単位で計測したい場合の対応方法
→SHOW PROFILESを使用します。

まず、SHOW PROFILESについて記載していきます。

SHOW PROFILESとは

SHOW PROFILESについて、公式に以下の記載があります。

現在のセッションの過程で実行されたステートメントのリソース使用状況を示すプロファイリング情報を表示します。

fw_error_www
fw_error_www

現在のセッションと記載されているので、セッション単位での計測となります。

セッション単位とは、複数人でMySQLサーバに入って「SHOW PROFILES」を実行した場合、自分が実行したSQLのみ「SHOW PROFILES」の結果として返されるということです。

言い換えると、他の人が実行したSQLは「SHOW PROFILES」の結果として出力されないということになります。

また、SHOW PROFILES ステートメントは非推奨と記載されています。

非推奨のようですが、簡単にクエリ実行速度をミリ秒単位で計測可能なステートメントなので使用させていただきます!

SHOW PROFILESのポイント

セッション単位での計測
→他の人が実行したSQLは計測対象とならない
非推奨のステートメント

それでは、実際にSHOW PROFILESを使ってみます!

SHOW PROFILESを実行してみる

まず、プロファイリングを有効にする必要があります。

以下のように「SET profiling = 1;」で有効にします。

mysql> SET profiling = 1;
Query OK, 0 rows affected, 1 warning (0.00 sec)

これで計測可能な状態となりましたので、以下のように計測したいSQLを実行します。

// 計測したいSQLを実行
mysql> select * from it_users ;
// セレクト結果
1 row in set (0.00 sec)

これで計測されましたので、「SHOW PROFILES;」で計測結果を確認します。

mysql> SHOW PROFILES;
+----------+------------+------------------------+
| Query_ID | Duration   | Query                  |
+----------+------------+------------------------+
|        1 | 0.00025150 | select * from it_users |
+----------+------------+------------------------+
1 row in set, 1 warning (0.00 sec)

DurationがSQL実行にかかった時間です。

単位は秒ですので、1000倍すればミリ秒に変換できます。

上の例では

0.00025150 × 1000 = 0.25150ミリ秒

となります。

まとめ

SHOW PROFILESを使用してクエリの実行時間をミリ秒単位で計測する方法について記載しました。

簡単に計測できましたが、SHOW PROFILESに到るまで結構苦労したので記事にさせて頂きました。

まとめ

SHOW PROFILESを使用することでミリ秒単位でSQLの実行速度を計測可能
SHOW PROFILESは非推奨のステートメント
→非推奨のようですが、これを使えば簡単にミリ秒単位での測定が可能

]]>
MySQLでインデックスを貼ったカラムを更新するとインデックスサイズが肥大化する https://it.kensan.net/mysql_index_update.html Sat, 29 Apr 2023 03:13:23 +0000 https://it.kensan.net/?p=1539 MySQLでインデックスを貼ったカラムを更新した場合のインデックスサイズとパフォーマンスへの影響を確認してみました。

確認した挙動

インデックスサイズ
パフォーマンス

Aurora MySQLを使い、MySQL5.7と8.0で、上記について確認しました!

確認した環境・条件

Aurora MySQLで確認
確認したMySQLのバージョンは5.7と8.0

テーブル内の全レコードについて、インデックスが貼られているカラムを更新

まずは、結果から記載していきます!

確認結果

テーブル内の全レコードについて、インデックスを貼ったカラムを更新した場合、以下の挙動となりました。

確認した挙動

MySQLバージョンでの違いはほとんど無し
インデックスサイズ
→インデックスサイズは2倍以上増加
SELECT・UPDATEのパフォーマンス
→多少劣化する
→UPDATEのパフォーマンスはアップデートする度に劣化していく
「OPTIMIZE TABLE」を実行すると、インデックスサイズは登録直後より小さくなり、パフォーマンスは登録直後と同等になる

 

結果詳細

結果の詳細です。

  • MySQLバージョンでの違いはほとんど無し

 

  • インデックスサイズ
    • 1回目の更新時に、2倍以上に増加
      • その後の更新処理では、ほとんど変化なし
        • OPTIMIZE TABLEすると登録直後より小さくなる
  • selectパフォーマンス
    • 1回目の更新後に、多少劣化
      • その後の更新処理では、ほとんど変化なし
        • OPTIMIZE TABLEすると登録直後と同程度になる
        • updateパフォーマンス
          • updateする度に、多少劣化していく

        以下、結果をまとめた表です!

        MySQL5.7 MySQL8.0
        登録直後インデックスサイズ 23MB 23MB
        登録直後selectパフォーマンス 0.00 sec 0.00 sec
        1回テーブル全件更新した際の
        パフォーマンス
        31.10 sec 31.18 sec
        1回テーブル全件更新した後の
        インデックスサイズ
        52MB 52MB
        1回テーブル全件更新した後の
        selectパフォーマンス
        0.01 sec 0.01 sec
        3回テーブル全件更新した際の
        パフォーマンス
        1 min 54.21 sec

        →1回あたり約38秒

        1 min 55.57 sec

        →1回あたり約38秒

        3回テーブル全件更新した後の
        インデックスサイズ
        50MB 50MB
        3回テーブル全件更新した後の
        selectパフォーマンス
        0.01 sec 0.02 sec
        10回テーブル全件更新した際の
        パフォーマンス
        6 min 46.46 sec

        →1回あたり約40秒

        6 min 50.47 sec

        →1回あたり約41秒

        10回テーブル全件更新した後の
        インデックスサイズ
        50MB 50MB
        10回テーブル全件更新した後の
        selectパフォーマンス
        0.01 sec 0.01 sec
        OPTIMIZE TABLEした後の
        インデックスサイズ
        16MB 16MB
        OPTIMIZE TABLEした後の
        selectパフォーマンス
        0.00 sec 0.00 sec

        準備

        挙動を確認するための準備をしていきます。

        準備すること

        テーブル作成
        データ作成用プロシージャーの作成
        データ更新用プロシージャーの作成

        まず、テーブルを作成してきます!

        テーブル作成

        以下のテーブルを用意します。

        create table user(
        id INT AUTO_INCREMENT,
        number INT,
        type INT,
        name VARCHAR(255),
        PRIMARY KEY (id),
        INDEX number_index (number)
        );

        データ作成用プロシージャー作成

        テストデータはプロシージャーで作成します。

        以下の通りデータ作成してくれるようになっています。

        • user.number:ランダムな数値を格納
        • user.type:カウントを2で割った余りを格納(あまり意味がないカラムです。)
        • user.name:「user_name_{カウント}」の値を格納
        -- 区切り文字を「//」に変更する
        DELIMITER //
        
        -- make_sample_dataというテストデータ作成用プロシージャーを作成する
        
        create procedure make_sample_data(in i int)
        begin
          declare count int default 0;
          -- 繰り返し
          while count < i do
            set count = count + 1;
            INSERT INTO user (number, type, name) VALUES(CEIL(RAND() * 1000), MOD(count,2),CONCAT('usr_name_',count));
          end while;
        end
        //
        -- 区切り文字を「;」に戻す
        DELIMITER ;

        データ更新用プロシージャー作成

        テストデータはプロシージャーで更新します。

        user.numberをランダムな数値で更新するようになっています。

        -- 区切り文字を「//」に変更する
        DELIMITER //
        
        -- update_sample_dataというテストデータ更新用プロシージャーを作成する
        
        create procedure update_sample_data(in i int)
        begin
          declare count int default 0;
          -- 繰り返し
          while count < i do
            set count = count + 1;
            -- number + 1で全テーブルデータを書き換える 
            UPDATE user
            SET number = CEIL(RAND() * 1000);
          end while;
        end
        //
        -- 区切り文字を「;」に戻す
        DELIMITER ;

        MySQL5.7で検証していきます!

        MySQL5.7での検証

        まずはバージョン確認から進めていきます。

        バージョン確認

        
        mysql> select version();
        +-----------+
        | version() |
        +-----------+
        | 5.7.12    |
        +-----------+
        1 row in set (0.00 sec)

        100万件のテストデータ登録

        ◾️100万件インサート

        mysql> call make_sample_data(1000000);
        Query OK, 1 row affected (1 min 6.98 sec)
        

        ◾️インデックスサイズ確認

         mysql> SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb  FROM mysql.innodb_index_stats WHERE index_name = 'number_index' and stat_name = 'size';
        +------------+--------------+-------------+
        | stat_value | index_name   | size_mb     |
        +------------+--------------+-------------+
        |       1507 | number_index | 23.54687500 |
        +------------+--------------+-------------+
        1 row in set (0.00 sec)
        

        ◾️登録直後のSELECTパフォーマンス(インデックスが貼られたカラムで絞り込み)

        mysql> select * from user where number = 100;
        1033 rows in set (0.00 sec)
        

        1回アップデート

        ◾️テーブル内の全レコードを1回更新

        mysql> call update_sample_data(1);
        Query OK, 998993 rows affected (31.10 sec)
        

        ◾️インデックスサイズ確認

        mysql> SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb FROM mysql.innodb_index_stats WHERE index_name = 'number_index' and stat_name = 'size';
        +------------+--------------+-------------+
        | stat_value | index_name   | size_mb     |
        +------------+--------------+-------------+
        |       3349 | number_index | 52.32812500 |
        +------------+--------------+-------------+
        1 row in set (0.00 sec)

        ◾️登録直後のSELECTパフォーマンス(インデックスが貼られたカラムで絞り込み)

        mysql> select * from user where number = 100;
        985 rows in set (0.01 sec)
        

        3回アップデート

        ◾️テーブル内の全レコードを3回更新

        mysql> call update_sample_data(3);
        Query OK, 998991 rows affected (1 min 54.21 sec)
        

        ◾️インデックスサイズ確認

        mysql> SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb FROM mysql.innodb_index_stats WHERE index_name = 'number_index' and stat_name = 'size';
        +------------+--------------+-------------+
        | stat_value | index_name   | size_mb     |
        +------------+--------------+-------------+
        |       3216 | number_index | 50.25000000 |
        +------------+--------------+-------------+
        1 row in set (0.00 sec)
        

        ◾️登録直後のSELECTパフォーマンス(インデックスが貼られたカラムで絞り込み)

        mysql> select * from user where number = 100;
        1000 rows in set (0.01 sec)
        

        10回アップデート

        ◾️テーブル内の全レコードを10回更新

        mysql> call update_sample_data(10);
        Query OK, 999013 rows affected (6 min 46.46 sec)
        

        ◾️インデックスサイズ確認

        mysql> SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb FROM mysql.innodb_index_stats WHERE index_name = 'number_index' and stat_name = 'size';
        +------------+--------------+-------------+
        | stat_value | index_name   | size_mb     |
        +------------+--------------+-------------+
        |       3207 | number_index | 50.10937500 |
        +------------+--------------+-------------+
        1 row in set (0.00 sec)
        

        ◾️登録直後のSELECTパフォーマンス(インデックスが貼られたカラムで絞り込み)

        mysql> select * from user where number = 100;
        952 rows in set (0.01 sec)
        

        OPTIMIZE TABLE する

        ◾️OPTIMIZE TABLEを実行する

        OPTIMIZE TABLE user;

        ◾️インデックスサイズ確認

        SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb FROM mysql.innodb_index_stats WHERE index_name = 'number_index' and stat_name = 'size';
        +------------+--------------+-------------+
        | stat_value | index_name   | size_mb     |
        +------------+--------------+-------------+
        |       1059 | number_index | 16.54687500 |
        +------------+--------------+-------------+
        1 row in set (0.00 sec)

        ◾️登録直後のSELECTパフォーマンス(インデックスが貼られたカラムで絞り込み)

        mysql> select * from user where number = 100;
        952 rows in set (0.00 sec)

        MySQL8.0で検証していきます!

        MySQL8.0での検証

        まずはバージョン確認から進めていきます。

        バージョン確認

        mysql> select version();
        +-----------+
        | version() |
        +-----------+
        | 8.0.26    |
        +-----------+
        1 row in set (0.02 sec)

        100万件のテストデータ登録

        ◾️100万件インサート

        mysql> call make_sample_data(1000000);
        Query OK, 1 row affected (1 min 2.48 sec)
        

        ◾️インデックスサイズ確認

         mysql> SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb  FROM mysql.innodb_index_stats WHERE index_name = 'number_index' and stat_name = 'size';
        +------------+--------------+-------------+
        | stat_value | index_name   | size_mb     |
        +------------+--------------+-------------+
        |       1507 | number_index | 23.54687500 |
        +------------+--------------+-------------+
        1 row in set (0.00 sec)
        

        ◾️登録直後のSELECTパフォーマンス(インデックスが貼られたカラムで絞り込み)

        mysql> select * from user where number = 100;
        1031 rows in set (0.00 sec)
        

        1回アップデート

        ◾️テーブル内の全レコードを1回更新

        mysql> call update_sample_data(1);
        Query OK, 999056 rows affected (31.18 sec)
        

        ◾️インデックスサイズ確認

        mysql> SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb FROM mysql.innodb_index_stats WHERE index_name = 'number_index' and stat_name = 'size';
        +------------+--------------+-------------+
        | stat_value | index_name   | size_mb     |
        +------------+--------------+-------------+
        |       3365 | number_index | 52.57812500 |
        +------------+--------------+-------------+
        1 row in set (0.01 sec)

        ◾️登録直後のSELECTパフォーマンス(インデックスが貼られたカラムで絞り込み)

        mysql> select * from user where number = 100;
        937 rows in set (0.01 sec)
        

        3回アップデート

        ◾️テーブル内の全レコードを3回更新

        mysql> call update_sample_data(3);
        Query OK, 999038 rows affected (1 min 55.57 sec)
        

        ◾️インデックスサイズ確認

        mysql> SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb FROM mysql.innodb_index_stats WHERE index_name = 'number_index' and stat_name = 'size';
        +------------+--------------+-------------+
        | stat_value | index_name   | size_mb     |
        +------------+--------------+-------------+
        |       3211 | number_index | 50.17187500 |
        +------------+--------------+-------------+
        1 row in set (0.00 sec)
        

        ◾️登録直後のSELECTパフォーマンス(インデックスが貼られたカラムで絞り込み)

        mysql> select * from user where number = 100;
        1075 rows in set (0.02 sec)
        

        10回アップデート

        ◾️テーブル内の全レコードを10回更新

        mysql> call update_sample_data(10);
        Query OK, 999025 rows affected (6 min 50.47 sec)
        

        ◾️インデックスサイズ確認

        mysql> SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb FROM mysql.innodb_index_stats WHERE index_name = 'number_index' and stat_name = 'size';
        +------------+--------------+-------------+
        | stat_value | index_name   | size_mb     |
        +------------+--------------+-------------+
        |       3207 | number_index | 50.10937500 |
        +------------+--------------+-------------+
        1 row in set (0.00 sec)
        

        ◾️登録直後のSELECTパフォーマンス(インデックスが貼られたカラムで絞り込み)

        mysql> select * from user where number = 100;
        1026 rows in set (0.01 sec)
        

        OPTIMIZE TABLE する

        ◾️OPTIMIZE TABLEを実行する

        OPTIMIZE TABLE user;

        ◾️インデックスサイズ確認

        SELECT stat_value, index_name, (stat_value * @@innodb_page_size) / 1024 / 1024 AS size_mb FROM mysql.innodb_index_stats WHERE index_name = 'number_index' and stat_name = 'size';
        +------------+--------------+-------------+
        | stat_value | index_name   | size_mb     |
        +------------+--------------+-------------+
        |       1059 | number_index | 16.54687500 |
        +------------+--------------+-------------+
        1 row in set (0.01 sec)
        

        ◾️登録直後のSELECTパフォーマンス(インデックスが貼られたカラムで絞り込み)

        mysql> select * from user where number = 100;
        1005 rows in set (0.00 sec)

        まとめ

        MySQLでインデックスを貼ったカラムを更新した場合のインデックスサイズとパフォーマンスへの影響を確認してみました。

        結果は以下の通りです!

        結果まとめ

        MySQLバージョンでの違いはほとんど無し
        インデックスサイズ
        →インデックスサイズは2倍以上増加
        SELECT・UPDATEのパフォーマンス
        →多少劣化する
        →UPDATEのパフォーマンスはアップデートする度に劣化していく
        「OPTIMIZE TABLE」を実行すると、インデックスサイズは登録直後より小さくなり、パフォーマンスは直後と同等になる

         

        MySQL5.7と8.0のパフォーマンス記事もありますので、よろしければ!

        MySQL5.7と8.0のパフォーマンスを比較検証してみましたー
        MySQL 5.7と比べると、8.0の方が性能が良いという噂を簡単な検証で確認してみました。 結論としては、 8.0の方がINSERTは早い。 8.0の方がSELECTも早いけど、正しくインデックス貼られていれば同程度。 UPDATE、DELETEは同程度。
        ]]>
        MySQL5.7と8.0のパフォーマンスを比較検証してみましたー https://it.kensan.net/mysql_performance.html Sat, 15 Apr 2023 03:00:01 +0000 https://it.kensan.net/?p=1225 MySQL 5.7と比べると、8.0の方が性能が良いという噂を簡単な検証で確認してみました。

        結論としては、

        • 8.0の方がINSERTは早い
        • 8.0の方がSELECTも早いけど、正しくインデックス貼られていれば同程度
        • UPDATE、DELETEは同程度

        という結果でしたー!

        なお、サポート期限は以下のようになっています。

        MySQLバージョン MySQL AWS RDSのMySQL AWS AuroraのMySQL
        5.7 2023年10月31日 2023 年 10 月 2024 年 10 月 31 日

        (Aurora バージョン2)

        8.0 2026年4月30日 未定 未定

        (Aurora バージョン3)

        Amazon RDS での MySQL のバージョン - Amazon Relational Database Service
        Amazon RDS for MySQL のバージョンについて学びます。
        Amazon Aurora バージョン - Amazon Aurora
        Amazon Aurora のバージョンについて説明します。バージョンには、それぞれのバージョン番号、リリースサイクル、バージョン廃止のタイムラインなどがあります。

        検証条件

        • 100万件と200万件のデータに対して、以下のパターンでCRUDの性能を検証
          • MySQL5.7インデックスなし
          • MySQL8.0インデックスなし
          • MySQL5.7インデックスあり
          • MySQL8.0インデックスあり

        検証結果

        100万件の検証結果

        MySQL5.7

        インデックスなし

        MySQL8.0

        インデックスなし

        MySQL5.7

        インデックスあり

        MySQL8.0

        インデックスあり

        結果比較
        100万件インサート 51.69 sec 47.43 sec 58.13 sec 57.23 sec 8.0の方が若干早い

        インデックスありだとインデックス構築分遅くなる

        100万件カウント 0.49 sec 0.07 sec 0.39 sec 0.07 sec 8.0の方が早い

        インデックス貼った方が早い

        1件SELECT 0.56 sec 0.54 sec 0.02 sec 0.00 sec 8.0の方が若干早い

        インデックス貼った方が早い

        1件UPDATE 0.77 sec 0.76 sec 0.01 sec 0.01 sec バージョンによる差はほとんどない

        インデックス貼った方が早い

        1件DELETE 0.86 sec 0.87 sec 0.04 sec 0.01 sec バージョンによる差はほとんどない

        インデックス貼った方が早い

        200万件の検証結果

        MySQL5.7

        インデックスなし

        MySQL8.0

        インデックスなし

        MySQL5.7

        インデックスあり

        MySQL8.0

        インデックスあり

        結果比較
        200万件インサート 1 min 42.67 sec 1 min 36.24 sec 1 min 55.69 sec 1 min 53.43 sec 8.0の方が若干早い

        インデックスありだとインデックス構築分遅くなる

        200万件カウント 0.84 sec 0.33 sec 0.75 sec 0.15 sec 8.0の方が早い

        インデックス貼った方が早い

        1件SELECT 1.74 sec 1.04 sec 0.02 sec 0.00 sec 8.0の方が若干早い

        インデックス貼った方が早い

        1件UPDATE 1.68 sec 1.69 sec 0.02 sec 0.00 sec バージョンによる差はほとんどない

        インデックス貼った方が早い

        1件DELETE 1.36 sec 1.82 sec 0.01 sec 0.01 sec バージョンによる差はほとんどない

        インデックス貼った方が早い

         

        検証環境

        AWS Aurora

        インスタンス:db.t3.medium

        準備

        テーブル作成

        以下のテーブルを用意します。インデックスなしテーブルです。

        create table user(
        id INT,
        type INT,
        name VARCHAR(255)
        );

        テストデータ作成用のプロシージャ作成

        テストデータはプロシージャーで作成します。

        以下の通りデータ作成してくれるようになっています。

        • user.id:カウントアップ形式で数値を格納
        • user.type:カウントを2で割った余りを格納(あまり意味がないカラムです。)
        • user.name:「user_name_{カウント}」の値を格納
        
        -- 区切り文字を「//」に変更する
        DELIMITER //
        
        -- make_sample_dataというテストデータ作成用プロシージャーを作成する
        
        create procedure make_sample_data(in i int)
        begin
          declare count int default 0;
          -- 繰り返し
          while count < i do
            set count = count + 1;
            INSERT INTO user VALUES(count, MOD(count,2),CONCAT('usr_name_',count));
          end while;
        end
        //
        -- 区切り文字を「;」に戻す
        DELIMITER ;

        インデックスなし

        MySQL 5.7検証

        バージョン確認

        select version();
        +-----------+
        | version() |
        +-----------+
        | 5.7.12 |
        +-----------+
        1 row in set (0.00 sec)

        100万件のパフォーマンス検証

        ◾️100万件インサート

        call make_sample_data(1000000);
        Query OK, 1 row affected (51.69 sec)

        ◾️100万件カウント

        select count(*) from user;
        +----------+
        | count(*) |
        +----------+
        | 1000000 |
        +----------+
        1 row in set (0.49 sec)

        ◾️1件SELECT

        select * from user where id = 500000;
        
        +--------+------+-----------------+
        | id | type | name |
        +--------+------+-----------------+
        | 500000 | 0 | usr_name_500000 |
        +--------+------+-----------------+
        1 row in set (0.56 sec)

        ◾️1件UPDATE

        update user set name = 'name_500001' where id = 500001;
        
        Query OK, 1 row affected (0.77 sec)
        Rows matched: 1 Changed: 1 Warnings: 0

        ◾️1件DELETE

        delete from user where id = 500002;
        
        Query OK, 1 row affected (0.86 sec)

        200万件のパフォーマンス検証

        テーブルを空にする

        TRUNCATE TABLE user;

        ◾️200万件インサート

        call make_sample_data(2000000);
        Query OK, 1 row affected (1 min 42.67 sec)

        ◾️200万件カウント

        select count(*) from user;
        +----------+
        | count(*) |
        +----------+
        | 2000000 |
        +----------+
        1 row in set (0.84 sec)

        ◾️1件SELECT

        select * from user where id = 1000000;
        
        +---------+------+------------------+
        | id | type | name |
        +---------+------+------------------+
        | 1000000 | 0 | usr_name_1000000 |
        +---------+------+------------------+
        1 row in set (1.74 sec)

        ◾️1件UPDATE

        update user set name = 'name_1000001' where id = 1000001;
        
        Query OK, 1 row affected (1.68 sec)
        Rows matched: 1 Changed: 1 Warnings: 0

        ◾️1件DELETE

        delete from user where id = 1000002;
        
        Query OK, 1 row affected (1.36 sec)

         

        MySQL 8.0検証

        バージョン確認

        select version();
        +-----------+
        | version() |
        +-----------+
        | 8.0.26 |
        +-----------+
        1 row in set (0.02 sec)

        100万件のパフォーマンス検証

        テーブルを空にする

        TRUNCATE TABLE user;

        ◾️100万件インサート

        call make_sample_data(1000000);
        Query OK, 1 row affected (47.43 sec)

        ◾️100万件カウント

        select count(*) from user;
        +----------+
        | count(*) |
        +----------+
        | 1000000 |
        +----------+
        1 row in set (0.07 sec)

        ◾️1件SELECT

        select * from user where id = 500000;
        
        +--------+------+-----------------+
        | id | type | name |
        +--------+------+-----------------+
        | 500000 | 0 | usr_name_500000 |
        +--------+------+-----------------+
        1 row in set (0.54 sec)

        ◾️1件UPDATE

        update user set name = 'name_500001' where id = 500001;
        
        Query OK, 1 row affected (0.76 sec)
        Rows matched: 1 Changed: 1 Warnings: 0

        ◾️1件DELETE

        delete from user where id = 500002;
        
        Query OK, 1 row affected (0.87 sec)

        200万件のパフォーマンス検証

        テーブルを空にする

        TRUNCATE TABLE user;

        ◾️200万件インサート

        call make_sample_data(2000000);
        Query OK, 1 row affected (1 min 36.24 sec)

        ◾️200万件カウント

        select count(*) from user;
        +----------+
        | count(*) |
        +----------+
        | 2000000 |
        +----------+
        1 row in set (0.33 sec)

        ◾️1件SELECT

        select * from user where id = 1000000;
        
        +---------+------+------------------+
        | id | type | name |
        +---------+------+------------------+
        | 1000000 | 0 | usr_name_1000000 |
        +---------+------+------------------+
        1 row in set (1.04 sec)

        ◾️1件UPDATE

        update user set name = 'name_1000001' where id = 1000001;
        
        Query OK, 1 row affected (1.69 sec)
        Rows matched: 1 Changed: 1 Warnings: 0

        ◾️1件DELETE

        delete from user where id = 1000002;
        
        Query OK, 1 row affected (1.82 sec)

        インデックスあり

        user.idにインデックスをはる

        alter table user add index id_index (id);

        MySQL 5.7検証

        100万件のパフォーマンス検証

        テーブルを空にする

        TRUNCATE TABLE user;

        ◾️100万件インサート

        call make_sample_data(1000000);
        Query OK, 1 row affected (58.13 sec)

        ◾️100万件カウント

        select count(*) from user;
        +----------+
        | count(*) |
        +----------+
        | 1000000 |
        +----------+
        1 row in set (0.39 sec)

        ◾️1件SELECT

        select * from user where id = 500000;
        
        +--------+------+-----------------+
        | id | type | name |
        +--------+------+-----------------+
        | 500000 | 0 | usr_name_500000 |
        +--------+------+-----------------+
        1 row in set (0.00 sec)

        ◾️1件UPDATE

        update user set name = 'name_500001' where id = 500001;
        
        Query OK, 1 row affected (0.01 sec)
        Rows matched: 1 Changed: 1 Warnings: 0

        ◾️1件DELETE

        delete from user where id = 500002;
        
        Query OK, 1 row affected (0.04 sec)

        200万件のパフォーマンス検証

        テーブルを空にする

        TRUNCATE TABLE user;

        ◾️200万件インサート

        call make_sample_data(2000000);
        Query OK, 1 row affected (1 min 55.69 sec)

        ◾️200万件カウント

        select count(*) from user;
        +----------+
        | count(*) |
        +----------+
        | 2000000 |
        +----------+
        1 row in set (0.75 sec)

        ◾️1件SELECT

        select * from user where id = 1000000;
        
        +---------+------+------------------+
        | id | type | name |
        +---------+------+------------------+
        | 1000000 | 0 | usr_name_1000000 |
        +---------+------+------------------+
        1 row in set (0.02 sec)

        ◾️1件UPDATE

        update user set name = 'name_1000001' where id = 1000001;
        
        Query OK, 1 row affected (0.02 sec)
        Rows matched: 1 Changed: 1 Warnings: 0

        ◾️1件DELETE

        delete from user where id = 1000002;
        
        Query OK, 1 row affected (0.01 sec)

        MySQL 8.0検証

        100万件のパフォーマンス検証

        テーブルを空にする

        TRUNCATE TABLE user;

        ◾️100万件インサート

        call make_sample_data(1000000);
        Query OK, 1 row affected (57.23 sec)

        ◾️100万件カウント

        select count(*) from user;
        +----------+
        | count(*) |
        +----------+
        | 1000000 |
        +----------+
        1 row in set (0.07 sec)

        ◾️1件SELECT

        select * from user where id = 500000;
        
        +--------+------+-----------------+
        | id | type | name |
        +--------+------+-----------------+
        | 500000 | 0 | usr_name_500000 |
        +--------+------+-----------------+
        1 row in set (0.00 sec)

        ◾️1件UPDATE

        update user set name = 'name_500001' where id = 500001;
        
        Query OK, 1 row affected (0.01 sec)
        Rows matched: 1 Changed: 1 Warnings: 0

        ◾️1件DELETE

        delete from user where id = 500002;
        
        Query OK, 1 row affected (0.01 sec)

        200万件のパフォーマンス検証

        テーブルを空にする

        TRUNCATE TABLE user;

        ◾️200万件インサート

        call make_sample_data(2000000);
        Query OK, 1 row affected (1 min 53.43 sec)

        ◾️200万件カウント

        select count(*) from user;
        +----------+
        | count(*) |
        +----------+
        | 2000000 |
        +----------+
        1 row in set (0.15 sec)

        ◾️1件SELECT

        select * from user where id = 1000000;
        
        +---------+------+------------------+
        | id | type | name |
        +---------+------+------------------+
        | 1000000 | 0 | usr_name_1000000 |
        +---------+------+------------------+
        1 row in set (0.00 sec)

        ◾️1件UPDATE

        update user set name = 'name_1000001' where id = 1000001;
        
        Query OK, 1 row affected (0.00 sec)
        Rows matched: 1 Changed: 1 Warnings: 0

        ◾️1件DELETE

        delete from user where id = 1000002;
        
        Query OK, 1 row affected (0.01 sec)
        女性誌多数掲載!
        普通の子に会える

        まとめ

        MySQL5.7と8.0のパフォーマンスを検証してみましたー!

        結果としては

        • 8.0の方がINSERTは早い
        • 8.0の方がSELECTも早いけど、正しくインデックス貼られていれば同程度
        • UPDATE、DELETEは同程度

        です!

        UPDATEは更新カラムにインデックス貼られてるか否かで結果が変わってくるかもと思いますが、簡単な検証結果ということで!

        MySQLのインデックスを貼ったカラムを更新するとインデックスサイズが肥大化するよ!という記事もありますので、よろしければ!

        MySQLでインデックスを貼ったカラムを更新するとインデックスサイズが肥大化する
        MySQLのインデックスを貼ったカラムを更新した場合のインデックスサイズとパフォーマンスへの影響を確認してみました。インデックスサイズは2倍以上増加し、SELECT・UPDATEのパフォーマンスは多少劣化するという結果となりました。
        ]]>