【GitHub Actions】download-artifact した csv ファイルをそのまま MySQL に高速インポートする

# 解決したい課題

- GitHub-Hosted Runner上のJob内で生成したcsvファイルを、Self-Hosted Runnerのジョブに渡して社内ネットワーク内のMySQLサーバーに接続したい
- csvデータ中身を読み込んでINSERTのSQLクエリを実行する処理を簡潔にしたい

# 解決する方法

MySQLには高速でテキストファイルを読み込んでテーブルに突っ込む `LOAD DATA STATEMENT` があるので、ワークフロー内でこれを使う。

https://dev.mysql.com/doc/refman/9.0/en/load-data.html

# YAML（GitHub Actionsのワークフローファイル）

CSVの生成は `GitHub-Hosted Runner` 上で、csvファイルのINSERTは `Self-Hosted Runner` 上でやりたいのでジョブを分けています。

```yaml:refresh-mysql-db.yml
name: reflesh-mysql-data
on:
  workflow_dispatch:

jobs:
  create-and-upload-csv:
    runs-on: ubuntu-latest
    steps:  
      - uses: actions/checkout@v4
      - name: Create CSV file
        run: |
          echo "id,name,age" > example.csv
          echo "1,Tomitarou,67" >> example.csv
      - name: Upload CSV as artifact
        uses: actions/upload-artifact@v4
        with:
          name: example-csv
          path: example.csv

  refresh-mysql-data:
    needs: create-and-upload-csv
    runs-on: [self-hosted, linux, x64]
    container: 
      # 今回は AWS ECR Public Gallery にある Docker の公式 MySQL イメージを使用
      # Pull Through Cache 機能を介してキャッシュされるようにしている
      image: xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-public/docker/library/mysql:latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4 # 前のジョブで作ったcsvをダウンロード
        with:
          name: example-csv
          path: downloaded
      - name: Refresh MySQL Data
        env:
          # あらかじめリポジトリの Secrets and variables に設定しておく
          MYSQL_HOST: ${{ vars.MYSQL_HOST }}
          MYSQL_USER: ${{ secrets.MYSQL_USER }}
          MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }}
          MYSQL_DB_NAME: ${{ vars.MYSQL_DB_NAME }}
        run: mysql --host=${MYSQL_HOST} --user=${MYSQL_USER} --password=${MYSQL_PASSWORD} ${MYSQL_DB_NAME} --local-infile=1 -e "$(cat path/to/refresh_data.sql)"
```

# SQLファイル

- ファイルPATH：`path/to/refresh_data.sql`
- サンプルとして、テーブルに対して`DELETE-INSERT` のクエリを投げるようにしている
- 対象のテーブル増やしたければ、csbとテーブルの指定を変えてSQLファイル内に追加するだけ

```sql:refresh_data.sql
START TRANSACTION;
DELETE FROM HOGE_TABLE;
LOAD DATA LOCAL INFILE "downloaded/example.csv" INTO TABLE HOGE_TABLE FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' IGNORE 1 LINES;
COMMIT;
```

# ちょっとだけ補足

- **YAML：**
  - MySQLクライアント起動時に指定する `--local-infile=1` オプション
    - MySQLサーバーにローカルファイル（クライアント側で生成されたファイル）を読み込むことを許可する設定
    - この設定値が 0 だと利用できないので注意（逆にセキュリティやデータの整合性を維持するために設定値を 0 にしておいた方がよい場合もあるので要確認）
- **SQL（LOAD DATA Statement）：**
  - `LOCAL`：
    - クライアントマシンにあるファイルを読み込むよう指定
  - `INFILE 'downloaded/example.csv'`：
    - MySQLにロードするデータファイルへのパスを指定
  - `INTO TABLE HOGE_TABLE`：
    - データをロードする対象のテーブルを指定
  - `FIELDS TERMINATED BY ','`：
    - 各フィールド（列）をどの文字で区切るかを明示的に指定
    - 今回はCSVなのでカンマ（`,`）を区切り文字として使う
  - `OPTIONALLY ENCLOSED BY '"'`：
    - フィールドの値が必要に応じて引用符（`"`）で囲まれていることを指定
    - フィールド値の中にカンマが含まれている場合の対策）
  - `IGNORE 1 LINES`：
    - CSVファイルの最初のヘッダー行を無視するために指定

# 注意点

- MySQLのトランザクションについて
  - `AUTOCOMMIT` オプション：MySQL はデフォルトで `ON` になっている
  - `LOAD DATA Statement`：`NDB strage engine` 使用時のみ暗黙的commitになる（トランザクション制御が不可能）

> The statements listed in this section (and any synonyms for them) implicitly end any transaction active in the current session, as if you had done a COMMIT before executing the statement.（訳：以下に書かれているstatementは暗黙的に現在のトランザクションを終了しちゃうよ）
> - ...
> - Data loading statements. LOAD DATA. LOAD DATA causes an implicit commit only for tables using the NDB storage engine.
> - ...

https://dev.mysql.com/doc/refman/9.0/en/server-system-variables.html

https://dev.mysql.com/doc/refman/9.0/en/server-system-variables.html

なので、トランザクション制御できるかどうか、ストレージエンジンを１度見たほうがよさげ。

```sql: strage engine の調査クエリ
SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'データベース名';
```

```terminal: 結果のサンプル
┌──────────────┬────────────┐
│  TABLE_NAME  │   ENGINE   │
├──────────────┼────────────┤
│ HOGE_TABLE   │   InnoDB   │ ← InnoDBはトランザクション制御できそう
│ FUGA_TABLE   │   InnoDB   │
│ PIYO_TABLE   │   InnoDB   │
└──────────────┴────────────┘
```


# 参考

https://qiita.com/sayama0402/items/e863ffb597ce87bf2e2f

https://zenn.dev/rescuenow/articles/5ec0940fea0c21