ニュース

Laravel 13.27の新機能:whereBinaryで実現するMySQLの厳密な大文字・小文字判定

照合順序は我々のコードを静かに書き換える。utf8mb4_unicode_ci は case を飲み込み、A7f3B9 と a7f3b9 を同じ行に重ねる。Laravel News によれば、Laravel 13.27.0 ではこの薄い境界に手を入れる whereBinary…

Laravel 13.27の新機能:whereBinaryで実現するMySQLの厳密な大文字・小文字判定

照合順序は我々のコードを静かに書き換える。utf8mb4_unicode_ci は case を飲み込み、A7f3B9a7f3b9 を同じ行に重ねる。Laravel News によれば、Laravel 13.27.0 ではこの薄い境界に手を入れる whereBinary が追加された。クエリビルダから出ず、バイト単位で同一性を判定する句が書ける。トークン列・スラッグ列・短いハッシュ列を扱う場面で、照合順序に起因する静かなバグの整理道具として使える。

比較しているのは文字列そのものではない

[MySQL の既定である utf8mb4_unicode](/articles/wordpressnosqliteben-fan-yun-yonghaxian/)_ci は collation-driven comparison を行う。判定しているのは文字列そのものではなく、文字列を照合順序に通した結果である。仕様上、case・アクセント・末尾空白はノイズとして吸収される。結果として、A7f3B9a7f3b9 も同じ行にマッチする。

表示名や記事タイトルなら、これが正解である。トークン列・スラッグ列・API キー列では同一性が崩れる。レビュー時には気付きにくく、静かにバグを待つパターンである。

これまでは whereRaw('BINARY column = ?', [$value]) で踏みとどまるのが定石だった。だがビルダの枠を外れる代償がある。when との合成、スコープ内の利用、他の条件式との連結が切れる。

whereBinary の貢献はそこにある。値は引き続きプレースホルダでバインドされ、パラメータ化の挙動は whereRaw 版と変わらない。変わるのはビルダ内部に留まり続ける点で、句として他の式と並ぶ単位で書ける。

増えた四つのメソッドと対応ドライバ

投入されたのは whereBinary だけではない。orWhereBinarywhereNotBinaryorWhereNotBinary も同時に揃った。等値と否定の二軸を押さえる構成である。

動作するドライバは MySQL と MariaDB である。MariaDB は MySQL の文法を継承するため、ドライバ別の対応は不要だった。

Postgres と SQLite では whereBinary を呼ぶと例外が飛ぶ。実装漏れではなく意図的なガードである。両者はそもそもデフォルトで大文字・小文字を区別するため、whereBinary を許しても実質 no-op に近い挙動にしかならない。whereLikecaseSensitive 引数が対応外ドライバで例外を投げるのと同じ設計である。

インデックス黙殺と二段構え

性能面の落とし穴にも目を向ける必要がある。既存のインデックスはカラムの照合順序に沿って構築されている。バイナリオペランドとの比較は別の照合順序で評価されるため、MySQL は原則としてそのインデックスを条件の充足に使えず、取得した行にフィルタを後付けするスキャンに落ちる。

小規模テーブルでは検出されない。だがトークン列のように候補数が爆発しうる列では、スキャンがそのまま応答時間に跳ねる。

定石は二段構えである。最初の条件でインデックスに候補を絞らせ、二つ目の whereBinary でバイト同一性を確定する。ルックアップと厳密比較を分担させる構造である。

恒久的な解は、最初からカラムの照合順序を binary 系あるいは大文字・小文字を区別する種類にしておくことである。インデックスとクエリが同じ照合順序で会話できるようになり、whereBinary を毎回指定する必要が消える。

一点、whereBinary で届かない層がある。_ci 系の照合順序に張られた unique インデックスは、挿入の時点で照合順序に従って重複を判定する。Adaada の二重登録は読み取り側ツールでは防げない。whereBinary はあくまでリード側の道具である。

現場での取り回し

本番が MySQL、テストが SQLite という構成では、whereBinary を仕込んだ瞬間 SQLite 側で例外が出る。テストスイートのドライバを MySQL に寄せるか、当該クエリだけ MySQL 接続に逃がす経路を用意する必要がある。

等値比較には whereBinary を、パターン一致には whereLike(..., caseSensitive: true) を使い分ける。発行される SQL は前者が =、後者が LIKE であり、クエリプランナの選択肢は前者の方が広い。パターンが必要ない場面で whereLike に逃げると、絞り込みの余地を削ることになる。

  • トークン列・スラッグ列・短いハッシュ列は、第一候補として whereBinary を据えると照合順序由来のバグの芽を潰せる。
  • 既に whereRaw('BINARY ... = ?', ...) で散在している記述は、whereBinary へ機械的に置換できる。バインドの挙動に変わりはない。
  • 恒久的に厳密化したい場合はカラム側の照合順序変更が本筋であり、whereBinary は読み取り側の補助輪と位置付けるのが筋である。
  • SQLite をテスト DB に据えている構成では、MySQL 接続での検証経路をクエリ単位で確保しておく。

Related reading: Laravelの主キー選定:UUIDと自動インクリメントIDが対立する現場の判断基準.

ニュースをもっと見る