🐛

PHP+MySQL で構築した CMS の一括編集機能とやらがバグってるから治せって言われた

2017/03/01に公開

状態

  • PHP5.4 + MySQL5.5 で構築されたオリジナル CMS
  • フレームワークは CakePHP2 系 (詳しいことは不明)
  • ものすごい件数のアイテムに属性をつけたり外したりする一括編集機能がある
  • ほとんどが設定した値になるが、一部ならないものがある
  • 更新対象は 1000 以上

ソースコードを確認

POST されたデータを for で回しつつ、そのアイテムが存在するか確認し、あれば update なければ insert みたいなことをやっていた。
1000件分。
最初に見たときは、 ゴミかと思った「これはメモリ食いつぶしてしまって途中で処理が止まったんじゃないか?」と思った。

実際は違った

リファクタリングして for ループを廃止し、クエリの発行も select, update, insert を1つずつ発行し、最大3回で済むように改善した。
だが、やはり保存されていないアイテムがあった。

ログを吐き出してみた

POST で受け取ったはずのアイテムデータの件数が少なかった。
更新対象は1000件以上あったはずなのに実際に受け取っているのは900件程度だった。

php.ini

POST の何らかの制限がかかっているのだろうと思い、 /etc/php.ini を開いてみた。
真っ先に確認したのは以下の3つ(最後のは今回は関係ないが念のため)。

  • memory_limit
  • post_max_size
  • upload_max_filesize
    これらの何もそれなりのサイズが設定されており、1000件程度のデータでオーバーするとは思えなかった。

max_input_vars

メモリ量じゃなければ単純に件数の制限がかかっているのかもしれないと思い確認して見ると、max_input_vars の行がコメントアウトされていた。
デフォルトでは 1000 らしい。
まさに。
とりあえずこの値を2000くらいにして見て Apache を再起動させて見ると、すべてのデータが保存されるようになった。
めでたしめでたし。

蛇足

バルクインサート

$model->save() を1回だけ書けば、普通 Insert のクエリは1回しか発行されないと思うのだが、CakePHP2 は 残念なことに件数分のクエリを発行する。
なので、自分で生クエリを生成するか、そういう関数を作っておく必要がある。
その際はインジェクション対策を自分で実装する必要があるので、しっかりテストするべし。
(なお Update は $model->updateAll() という関数が準備されている。ただし一癖あるので良くテストしたほうがいい)

理想

INSERT INTO table ( name, status, created, modified ) VALUES ( 'name1', 1, now(), now() ),  ( 'name2', 1, now(), now() ),  ( 'name3', 1, now(), now() );

現実

INSERT INTO table ( name, status, created, modified ) VALUES ( 'name1', 1, now(), now() );
INSERT INTO table ( name, status, created, modified ) VALUES ( 'name2', 1, now(), now() );
INSERT INTO table ( name, status, created, modified ) VALUES ( 'name3', 1, now(), now() );

Discussion