MySQLは5.6系から時刻系のデータ型がミリ秒を持てるようになった。
やっとかよって感じですが。
最近持てるようになったので、Railsでは4.1まではMySQLに対してクエリを投げる時はミリ秒以下は問答無用で切り捨ててクエリを構築していた。
しかし、4.2betaが出る辺りで、MySQLにミリ秒対応のクエリを送るPRがマージされた。
MySQL 5.6 Fractional Seconds by arthurnn · Pull Request #14359 · rails/rails
既にMySQL側のカラムがミリ秒精度を持っている場合は特に問題は起きない。
けど、RailsでMySQL使っててRailsのバージョン上げようかって時に、そんなことはほぼ無いだろうと思う。
その場合、ちょっと面倒なことになる。
MySQL5.6がミリ秒以下の値を受け取った場合、精度が足りない部分は四捨五入(round)される。
ということは、今まで、12:00:00.5
という0.5秒を含むような時刻を代入してActiveRecord経由で保存した場合、12:00:00
として送信されていたのが、12:00:00.500000
と送信されてしまい、DBに保存される値が12:00:01
になってしまう。
大体半分ぐらいの確率で今まで想定していた挙動から1秒ズレた値が格納される可能性がある。
1秒ズレるぐらい問題無いんであれば、テストコードを多少修正するぐらいで済むかもしれませんが、Ruby上でTimel#to_i
した値が、保存前と再読み込み後で変化するってのはかなり気持ち悪い気がする。
対応方針としてどうするかを考える。
- スルーしてテストコードで引っかかる所だけ直す
- カラムの型を更新してMySQLにミリ秒を持たせる (kamipoさんのPRがマージされればmigrationで処理できる)
- ActiveRecordにモンキーパッチを当ててミリ秒対応を無効化
- ActiveRecordにモンキーパッチを当てて属性値が代入された時点でミリ秒以下を切り捨てる
- datetimeの精度を判別してSQLクエリを調整する (kamipoさんのPRがマージされれば出来なくはなさそう)
ref. Allow precision option for MySQL datetimes by kamipo · Pull Request #17673 · rails/rails
そもそもミリ秒程度の誤差が気にならない場合、スルーしてテストが刺さる箇所だけ直せばそれで済むような気がするが、保存前と読み出した値で#to_i
の値が変わるのは割とまずい気がする。
Rubyは切り捨てるが、MySQLは四捨五入というのが危ない。
MySQLにミリ秒を持たせるのが一番妥当な気がするがカラム変更は負荷が高いし余りやりたくない感じがある。
とにかく安全策を取りたいなら以下のコミットの箇所をモンキーパッチで潰して戻すのが一番安全な気がする。
https://github.com/rails/rails/commit/df5a38fc6aeb8dfaa816fcbe0efb3fe4de169833
(ちなみに、masterではkamipoさんのリファクタリングによってabstract_adapterに移っている)
うーん、イマイチどうすればいいか分からん。
しかし、やはりRails+MySQLの闇に触れるとkamipo神に行き着くので、マジで足向けて寝られない。