【MySQL】【MariaDB】 "mysqldump" "diff" コマンドを使い データベーススキーマの差分チェックを行った話

## 経緯

業務の中で、運用8年目に突入するコンテンツがあり、各環境へのスキーマ更新を行った後の反映漏れの確認に `mysqldiff` を使いデータベーススキーマの差分チェックを行っていた。

ただ8年も運用を重ねていると、差分比較のテーブル数がマスターテーブルだけで200を超え、`mysqldiff` の実行結果が返ってくるまでに数分かかり反映作業のネックになっていた。

## やりたい事
* 短時間で差分結果を知りたい
* できればツールなどは使わずに手軽に行いたい

## 結果

`mysqldump --no-data` で、各環境のスキーマ定義だけを出力し、`diff` コマンドで差分を取ることでひとまず解決

最終的に以下のようなコマンドで確認を行う

```
diff -F "^CREATE" -c -I "Dump completed on" \
  <(mysqldump --no-data -h HOST_NAME  -P PORT -u USER_NAME -p DB_NAME | sed 's/\(AUTO_INCREMENT=\)[0-9][0-9]*/\1/') \
  <(mysqldump --no-data -h HOST_NAME  -P PORT -u USER_NAME -p DB_NAME | sed 's/\(AUTO_INCREMENT=\)[0-9][0-9]*/\1/')
```

## 具体例として

#### 以下のような状況を想定し、2つのデータベースを用意してみる
* diff_db_pro 　⇒　本番環境のDB
* diff_db_stg 　⇒　ステージング環境のDB

```
create database diff_db_pro;
create database diff_db_stg;
```

#### それぞれ3つのテーブル「shop」「item」「user」を作成
##### shopテーブル
* STG環境のみカラムが追加されており、PRO環境ではカラムの追加が漏れている状況
* shop_id　には AUTO_INCREMENT が設定されており、AUTO_INCREMENT の値がそれぞれのDBで異なる状況となる

```
MariaDB [diff_db_pro]> show create table shop;
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                             |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| shop  | CREATE TABLE `shop` (
  `shop_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `name` text NOT NULL,
  `in_date` datetime NOT NULL,
  `up_date` datetime NOT NULL,
  PRIMARY KEY (`shop_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.000 sec)
```

```
MariaDB [diff_db_stg]> show create table shop;
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                                                           |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| shop  | CREATE TABLE `shop` (
  `shop_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `name` text NOT NULL,
  `add_column` text NOT NULL,
  `in_date` datetime NOT NULL,
  `up_date` datetime NOT NULL,
  PRIMARY KEY (`shop_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.000 sec)
```

##### itemテーブル
* itemテーブルついては、差分が無い状況
```
MariaDB [diff_db_stg]> show create table item;
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                             |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| item  | CREATE TABLE `item` (
  `item_id` smallint(5) unsigned NOT NULL,
  `name` text NOT NULL,
  `in_date` datetime NOT NULL,
  `up_date` datetime NOT NULL,
  PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.000 sec)

```

##### userテーブル
* 本番環境ににみ（diff_db_pro）、PKに PARTITION の設定がされている状況
```
MariaDB [diff_db_pro]> show create table user;
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                                             |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| user  | CREATE TABLE `user` (
  `user_id` int(10) unsigned NOT NULL,
  `name` text NOT NULL,
  `in_date` datetime NOT NULL,
  `up_date` datetime NOT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
 PARTITION BY LINEAR HASH (`user_id`)
PARTITIONS 10 |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.000 sec)
```

- - - 
この状態で、差分を`diff` コマンドの context形式で出力してみる
※`diff` コマンドについては[コチラ](https://atmarkit.itmedia.co.jp/ait/articles/1704/14/news011.html "diffリンク")を参考

```
$ diff  -c  \
>   <(mysqldump --no-data -u■■■■■■■ -p■■■■■■ diff_db_pro) \
>   <(mysqldump --no-data -u■■■■■■■ -p■■■■■■ diff_db_stg)
*** /dev/fd/63  2025-02-10 20:49:08.945826829 +0900
--- /dev/fd/62  2025-02-10 20:49:08.945826829 +0900
***************
*** 1,6 ****
  -- MySQL dump 10.17  Distrib 10.3.13-MariaDB, for Linux (x86_64)
  --
! -- Host: localhost    Database: diff_db_pro
  -- ------------------------------------------------------
  -- Server version     10.3.13-MariaDB-log

--- 1,6 ----
  -- MySQL dump 10.17  Distrib 10.3.13-MariaDB, for Linux (x86_64)
  --
! -- Host: localhost    Database: diff_db_stg
  -- ------------------------------------------------------
  -- Server version     10.3.13-MariaDB-log

***************
*** 41,50 ****
  CREATE TABLE `shop` (
    `shop_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
    `name` text NOT NULL,
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`shop_id`)
! ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
  /*!40101 SET character_set_client = @saved_cs_client */;

  --
--- 41,51 ----
  CREATE TABLE `shop` (
    `shop_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
    `name` text NOT NULL,
+   `add_column` text NOT NULL,
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`shop_id`)
! ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
  /*!40101 SET character_set_client = @saved_cs_client */;

  --
***************
*** 60,68 ****
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`user_id`)
! ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
!  PARTITION BY LINEAR HASH (`user_id`)
! PARTITIONS 10;
  /*!40101 SET character_set_client = @saved_cs_client */;
  /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

--- 61,67 ----
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`user_id`)
! ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  /*!40101 SET character_set_client = @saved_cs_client */;
  /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
```

差分として、
「データベース名」「AUTO_INCREMENT値」「追加したカラム（add_column）」「PARTITIONの設定」が出力される

* * * 

このままだと、`差分としてPARTITIONがある userテーブル名`が出力されておらず対象テーブルが分からない為、diff コマンドのオプションに　`-F "^CREATE"`　をつけてみる
`※差分があった場合、前の行にさかのぼってマッチする行を表示してくれる。`

| -F 正規表現 | --show-function-line=正規表現 | context形式とunified形式で、各差分のブロックに対し、その前方がパターンにマッチした最後の行の一部を表示 |
|:-----------|------------:|:------------:|

```
$ diff -F "^CREATE" -c \
>   <(mysqldump --no-data -u■■■■■■■ -p■■■■■■ diff_db_pro) \
>   <(mysqldump --no-data -u■■■■■■■ -p■■■■■■ diff_db_stg)
*** /dev/fd/63  2025-02-10 21:31:26.109432389 +0900
--- /dev/fd/62  2025-02-10 21:31:26.109432389 +0900
***************
*** 1,6 ****
  -- MySQL dump 10.17  Distrib 10.3.13-MariaDB, for Linux (x86_64)
  --
! -- Host: localhost    Database: diff_db_pro
  -- ------------------------------------------------------
  -- Server version     10.3.13-MariaDB-log

--- 1,6 ----
  -- MySQL dump 10.17  Distrib 10.3.13-MariaDB, for Linux (x86_64)
  --
! -- Host: localhost    Database: diff_db_stg
  -- ------------------------------------------------------
  -- Server version     10.3.13-MariaDB-log

*************** CREATE TABLE `item` (
*** 41,50 ****
  CREATE TABLE `shop` (
    `shop_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
    `name` text NOT NULL,
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`shop_id`)
! ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
  /*!40101 SET character_set_client = @saved_cs_client */;

  --
--- 41,51 ----
  CREATE TABLE `shop` (
    `shop_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
    `name` text NOT NULL,
+   `add_column` text NOT NULL,
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`shop_id`)
! ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
  /*!40101 SET character_set_client = @saved_cs_client */;

  --
*************** CREATE TABLE `user` (
*** 60,68 ****
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`user_id`)
! ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
!  PARTITION BY LINEAR HASH (`user_id`)
! PARTITIONS 10;
  /*!40101 SET character_set_client = @saved_cs_client */;
  /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

--- 61,67 ----
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`user_id`)
! ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  /*!40101 SET character_set_client = @saved_cs_client */;
  /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
```

これで、PARTITIONがある `user`テーブル名もわかるようになったが、`AUTO_INCREMENT` の値が環境によって異なる事が多く、差分として出力されないようになればより見やすくなる

`sed`コマンドを使い `AUTO_INCREMENT=値` の値を出力しないことで、差分対象から除くようにしてみる

```
diff -F "^CREATE" -c \
>   <(mysqldump --no-data -u■■■■■■■ -p■■■■■■ diff_db_pro | sed 's/\(AUTO_INCREMENT=\)[0-9][0-9]*/\1/') \
>   <(mysqldump --no-data -u■■■■■■■ -p■■■■■■ diff_db_stg | sed 's/\(AUTO_INCREMENT=\)[0-9][0-9]*/\1/')
*** /dev/fd/63  2025-02-12 16:12:59.067101589 +0900
--- /dev/fd/62  2025-02-12 16:12:59.067101589 +0900
***************
*** 1,6 ****
  -- MySQL dump 10.17  Distrib 10.3.13-MariaDB, for Linux (x86_64)
  --
! -- Host: localhost    Database: diff_db_pro
  -- ------------------------------------------------------
  -- Server version     10.3.13-MariaDB-log

--- 1,6 ----
  -- MySQL dump 10.17  Distrib 10.3.13-MariaDB, for Linux (x86_64)
  --
! -- Host: localhost    Database: diff_db_stg
  -- ------------------------------------------------------
  -- Server version     10.3.13-MariaDB-log

*************** CREATE TABLE `item` (
*** 41,46 ****
--- 41,47 ----
  CREATE TABLE `shop` (
    `shop_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
    `name` text NOT NULL,
+   `add_column` text NOT NULL,
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`shop_id`)
*************** CREATE TABLE `user` (
*** 60,68 ****
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`user_id`)
! ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
!  PARTITION BY LINEAR HASH (`user_id`)
! PARTITIONS 10;
  /*!40101 SET character_set_client = @saved_cs_client */;
  /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

--- 61,67 ----
    `in_date` datetime NOT NULL,
    `up_date` datetime NOT NULL,
    PRIMARY KEY (`user_id`)
! ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  /*!40101 SET character_set_client = @saved_cs_client */;
  /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
```

 これで `AUTO_INCREMENT=値` の行も、値が異なるだけでは差分として出力されないようになった

* * * 

もし、ダンプ末尾の　`-- Dump completed on XXXX-XX-XX `
という表示も差分として表示されて外したい場合には、`-I "Dump completed on" \` をつける事で解決できる

## テーブルスキーマの差分が無くなった場合

最終的に、diff_db_pro のほうにも、
`shop`テーブルに `add_column`、 'user'テーブルに `PARTITION` が追加された状態で差分チェックを行うと

```
diff -F "^CREATE" -c -I "Dump completed on" \
>   <(mysqldump --no-data -u■■■■■■■ -p■■■■■■ diff_db_pro | sed 's/\(AUTO_INCREMENT=\)[0-9][0-9]*/\1/') \
>   <(mysqldump --no-data -u■■■■■■■ -p■■■■■■ diff_db_stg | sed 's/\(AUTO_INCREMENT=\)[0-9][0-9]*/\1/')
*** /dev/fd/63  2025-02-12 16:28:06.593081984 +0900
--- /dev/fd/62  2025-02-12 16:28:06.594082073 +0900
***************
*** 1,6 ****
  -- MySQL dump 10.17  Distrib 10.3.13-MariaDB, for Linux (x86_64)
  --
! -- Host: localhost    Database: diff_db_pro
  -- ------------------------------------------------------
  -- Server version     10.3.13-MariaDB-log

--- 1,6 ----
  -- MySQL dump 10.17  Distrib 10.3.13-MariaDB, for Linux (x86_64)
  --
! -- Host: localhost    Database: diff_db_stg
  -- ------------------------------------------------------
  -- Server version     10.3.13-MariaDB-log
```

差分として、データベース名だけが出力されるようになり、差分がない事がわかるようになる

## まとめ
今回の、 `mysqldump --no-data` の結果を `diff`コマンドで差分を取ることによって、
今まで mysqldiff で数分時間を取られていた作業が、1～2秒で終える結果となり、本番環境へのリリース作業において、大幅な時間短縮につながる結果となった。

## 参考記事
http://68user.blog27.fc2.com/blog-entry-44.html

https://atmarkit.itmedia.co.jp/ait/articles/1704/14/news011.html