#はじめに
アプリ開発中に起こったMySQLのエラーについて忘備録も兼ねて記録しておこうと思います。
#エラー文
rails g devise user
でuserモデルを作成し、rails db:migrate
を行うと下記のエラー文が表示。
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にする
今回は簡単そうな①でエラー解決していきます。
__結論 : 文字制限をかけるファイルを新規作成する__
現在の入力できる最大文字数、__255文字『varchar(255)』__を__191文字『varchar(191)』__に__変更__できる__ファイル__を新規作成し、__767バイトを超えない__ようにします。
__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
という設定を__上書き__することができます。
ソースはこちら
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にて確認することができます。
[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__になっているのがわかりますね。
[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