MySQL8(多分それ以前のバージョンでも)にCSVデータをインポートするときのベストな方法

#前提
この記事は以下のような方にお勧めいたします。

- DBの開発中で、MySQLサーバーとクライアントは同じマシン
- MySQLでcsvファイルから大量のデータを入力したい
- MySQL Workbenchからのインポートは遅すぎると苛立ちがちの方
- "ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement"と表示されて実行できない方

#実行環境
- MySQL8.0.16
- Windows10
- サーバーとクライアントは同じマシン（テスト環境）

#secure-file-privを正しく設定する
MySQLではサーバー上のローカルファイルからのインポートに、セキュリティの観点から制限をかけています。
これを回避して[^1]ファイルからデータをインポートするためには、MySQLサーバーに入出力の権限のあるフォルダを指定してあげます。
[^1]: 回避という表現は適切ではないと思いますが
##my.iniに設定を追加する
設定の方法は単純です。サーバーへの設定ですのでmy.ini（またはmy.confなど）にアクセスを許可するフォルダを指定します。

```text:my.ini
[mysqld]
#何かしらのその他の設定...
secure_file_priv=D:\import  #長いパス打ちたくないので浅い場所に専用フォルダを作った
```
上記の1行を追加するだけです。
既にサーバーを起動しているのであれば、設定は起動時に読み込まれるので再起動します。

##設定の確認
サーバーにログインして、サーバーの変数を確認します。

```sql
mysql> SELECT @@GLOBAL.secure_file_priv;
+---------------------------------------------+
| @@GLOBAL.secure_file_priv                   |
+---------------------------------------------+
| #指定したパスが表示されるはずです              |
+---------------------------------------------+
```

上記が確認できたら、実際にコマンドを利用してインポートを行います。

#コマンドラインからCSVをインポートする
##LOAD DATA INFILE構文
MySQLでファイルからデータをインポートするには[LOAD DATA INFILE構文](https://dev.mysql.com/doc/refman/5.6/ja/load-data.html)を使用します。[公式のドキュメントによると、INSERTの20倍速い](https://dev.mysql.com/doc/refman/5.6/ja/insert-speed.html)とのことです。

先に読み込むファイルの書式を確認しておきましょう。この記事では下記の書式を想定しています。

- 文字コード：JIS（レガシーなデータを移行しようとしています）
- データ形式はCSV（データの区切りはカンマ、文字列（データ）はダブルクォーテーションで括られています。）
- 改行文字は\r\f（Windowsを想定しています）
- ファイルの1行目はヘッダ行で実データではありません
- テーブルの構造とファイルのデータの順は一致しています

##LOAD DATA INFILE構文の利用例
上記のようなcsvファイルを読み込むのであれば、コマンドは下記のようになります。

```sql
load data infile 'D:\\import\\for-import.csv' #前述の例のようにsecure_file_privを指定したものとして
into table `new_table`     #インポート先のテーブル名
character set sjis         #ファイルの文字セット（JISはSJISに含まれるためSJISを指定）
fields terminated by ','   #データフィールドの区切り文字（csvなのでカンマ）
enclosed by '"'            #データはダブルクォーテーションで括られている
lines terminated by '\r\n' #行末文字は\r\f
ignore 1 lines;            #1行目はヘッダなので読み込まない
```

パスを指定する際にリテラルとして扱うため、Windowsではバックスラッシュをエスケープする必要があります。
`character set`を指定することで、事前に文字セットを変換する必要がありません。ただし、ファイルに書き出された時点でテーブル内のすべてのカラムが一つの文字セットに統一されている必要があります。これは書き出しの時点の問題で、読み込み時には解決できないようです。
`fields`以降でフィールド（カラム）の解釈を指定します。`terminated by`でフィールドの区切り文字、`enclosed by`でデータを括っている文字を指定できます。また、`escaped by`でエスケープに使用される文字も指定できます。
同様に、`lines`以降で行に対する解釈を指定できます。`terminated by`で行の終わりを表す文字を指定します。Windowsでは通常、"\r\n"です。
`ignore n lines`で読み飛ばす行数を指定できます。

インポート先のテーブルと読み込むファイルのフィールド（カラム）の数が一致しているケースであれば、これでインポートが可能です。

```sql
Query OK, 30164 rows affected (4.07 sec)
Records: 30164  Deleted: 0  Skipped: 0  Warnings: 0
```
無事読み込みに成功したのであれば、上記のような結果になります。

#まとめ
私はこれが、csvファイルをインポートするときの**ベストの手順**だと思います。


#補記
##読み込み先のカラム、順序の指定
列とカラムの数が一致しなかったり、フィールドとカラムの順序が異なる場合は、別途、読み込み先のカラム名を読み込みの順序に合わせて指定する必要があります。
[公式ドキュメント](https://dev.mysql.com/doc/refman/5.6/ja/load-data.html)のカラムリストについてを参照してください。
##読み込みの前処理
MySQL8では欠落したフィールドを埋めることができるように[機能が追加](https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-input-preprocessing)されています。上記のカラムリストと合わせて使用します。

##`local`と読み込みファイルの位置について
LOAD DATA INFILE構文を`LOAD DATA LOCAL INFILE`として使用した場合、クライアントプログラムがファイルを読み込んでサーバーへ送信する、という挙動になります。
これは（サーバーとクライアントが同一マシンである）開発環境では無駄な動作ですが、**リモートのサーバーにデータファイルをアップロードする場合には必要**になります。
上記の例ではlocalを使用していないため、（たまたまクライアントマシンと同じマシン上で起動している）mysqlサーバーが直接、ファイルの読み込みを行っています。この時、セキュリティ対策として権限のある指定されたフォルダからのみファイルを読み込む設定が、`secure_file_priv`となります。これに空白を指定するような説明がされていることが多くありますが、それは**バッドプラクティス**です。
当然ながら、サーバーマシンのシェルにリモートクライアントからログインし、サーバー上のファイルを読み取る場合はlocalの指定は不要で、この記事のケースのように`secure_file_priv`を指定しておく必要があります。その際も、もともとMySQLがアクセスできるフォルダを指定するのが安全だと思います。