【Mysqlエラー】Mysql2::Error: Specified key was too long; max key length is 767 bytes

#はじめに

アプリ開発中に起こったMySQLのエラーについて忘備録も兼ねて記録しておこうと思います。

#エラー文

`rails g devise user`でuserモデルを作成し、`rails db:migrate`を行うと下記のエラー文が表示。

```sql

Mysql2::Error: Specified key was too long; max key length is 767 bytes
```

直訳すると「指定したキーは長すぎます。キーの最大範囲は767バイトまでです」

意味は「Mysql2に格納できる最大文字データ量(767バイト)に対してオーバーしてしまったためエラーになった」ということのようです。

#エラー解決に向けての予備知識

##utf8
文字コードの中で世界で最も普及している文字コード。
通常は1～4バイトで文字を表現するが、MySQLでは**3バイト**の文字までしか扱えない。
__1文字当たり3バイト。__

##utf8mb4
データベースMySQLで扱うための文字コード。
__4バイト__の文字を扱えるため、絵文字を扱いたい場合はこちらを使用する
→絵文字は4バイトなので、3バイトまでしか扱えないutf8では不可能なため。
__1文字当たり4バイト。__

##VARCHAR型
・可変長(長さを変えられる)文字列のこと

・varchar(m)という形で指定する。(mはバイト数。0~65535まで。)

__例) varchar(255)　varchar(191)__

・char型と異なり、末尾に空白は付かない。
・末尾に空白が付いた文字列はそのまま格納される。
↑の場合、取得時も空白が付いたままだが、WHERE句での比較時には削除された状態で比較が行われる。



#エラー考察
前提条件として

MySQLのインデックスは最大__767バイト__。

Railsのstringにおけるデフォルト値が__255文字__（MySQLで保存できるデータ量）。


* __3バイト__のutf8では

　__3バイト　×　255文字　=　765バイト__　＜　767バイト



* __4バイト__のutf8mb4では

　__4バイト　×　255文字　=　1020バイト__　＞　767バイト

つまり、上記のように__文字コードutf8mb4__では、
__4 × 255 = 1020バイト__となって、MySQLのインデックスの最大数である__767バイト__を上回ってしまいエラーが発生してしまったということです。



#解決方法

解決方法は大きく2つあるようで、

①767バイトを超えないように最大文字数を削る
②767バイトを超えてもOKにする

今回は簡単そうな①でエラー解決していきます。

<br>
__結論 : 文字制限をかけるファイルを新規作成する__


現在の入力できる最大文字数、__255文字『varchar(255)』__を__191文字『varchar(191)』__に__変更__できる__ファイル__を新規作成し、__767バイトを超えない__ようにします。

<br>
__191文字『varchar(191)』__の理由は

MySQLのインデックスは最大__767バイト__
utf8mb4は__1文字当たり4バイト__必要なので

__767バイト　÷　4バイト　=　191.75文字__　


#実際のコード
`mysql.rb`を`config/initializer`配下に新規作成し__varchar__の__limit__を__191__にしましょう。

この`mysql.rb`というファイルによって__ActiveRecord__内の`NATIVE_DATABASE_TYPES`という設定を__上書き__することができます。

ソースは[こちら](https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb)

```config/initializer/mysql.rb
require 'active_record/connection_adapters/abstract_mysql_adapter'

module ActiveRecord
  module ConnectionAdapters
    class AbstractMysqlAdapter
      NATIVE_DATABASE_TYPES[:string] = { :name => "varchar", :limit => 191 } #注目
    end
  end
end
```

##mysql.rb作成前
rails consoleにて確認することができます。

```console
[1] pry(main)> ActiveRecord::Base.connection.native_database_types
=> {:primary_key=>"bigint auto_increment PRIMARY KEY",
 :string=>{:name=>"varchar", :limit=>255}, #limitが255になっている
#...以下略
```

##mysql.rb作成後

:string=>{:name=>"varchar", :limit=>__191__}と最大文字数が__191__になっているのがわかりますね。

```console
[2] pry(main)> ActiveRecord::Base.connection.native_database_types
=> {:primary_key=>"bigint auto_increment PRIMARY KEY",
 :string=>{:name=>"varchar", :limit=>191}, #ここがmysql.rbで設定した内容に変わる
#...以下略
```

これで文字のデータ量を767バイト以内に納めることができました。

この後rails db:migrateを入力すると上手く作動しエラーが解消されました


#ちなみに

今回のように、最大文字数を255文字から191文字にする方法ではなく、
最初に`config/database.yml`に記載した文字コードを途中から変更(今回であればutf8mb4からutf8に変更してマイグレーション)する方法を取ると、もっと面倒臭いエラーになるようです。

rails db:migrateをすると消えない謎のファイルが現れ、モデルの作り直しになってしまうとか。


__database.yml__の記載された情報は、迂闊に手を出せないので慎重に取り扱う必要がありますね。



#参考記事
[Qiita記事【MySQL】Mysql2::Error: Specified key was too long; max key length is 767 bytes](https://qiita.com/terufumi1122/items/9ea764618eba01144e09)

[Qiita記事 utf8mb4で巻き起こったトラブル](https://qiita.com/jkr_2255/items/74fc79e764378b59355a)

[Qiita記事 【SQL】char型とvarchar型の違いについてまとめてみた](https://qiita.com/nuts_468/items/4db32c8066c1d0772c6b)

[MySQL 5.5 / Redmine 3.4.3 / Ruby23-x64 --- Mysql2::Error: Specified key was too long; max key length is 767 bytes
](https://www.redmine.org/boards/2/topics/54308)

[MySQLでutf8とutfmb4の混在で起きること](https://tmtms.hatenablog.com/entry/2016/09/06/mysql-utf8)