ファイルを物理ファイルではなく、データベースに保存する
tableの設計
- おそらく2択
MEDIUMBLOB
- およそ 16MB(2^24 - 1)
- 常識的な添付ファイルで使えるサイズ。
LONGBLOB
- およそ 4GB(2^32 - 1)
- 本当に4GBを格納するつもりならば考え直した方が良い。
- とはいえ、16MB以上であれば必然的にこのサイズ。
TINYBLOB
- およそ 255B
- テキストくらいしかないだろうし、それならバイナリで持たせる必要ないかな。
BLOB
- およそ 64KB
- プロフィール画像とか?
- 昔、BLOBってんだからバイナリ無制限に入るんだろうとか思ってた。そもそも無制限とかありえないし実際全然入らなかった死にたい。
MySQLの設定
max_allowed_packet
確認
show variables like 'max_allowed_packet';
| max_allowed_packet | 1048576 |
- クライアントからサーバに送ることができるパケットの最大値設定
- この値を超えるとエラーを吐くので、割とすぐ気付く。
- デフォルトでは1MB
-
最大値でも1GB
- なので、LONGBLOBの最大の4GBは保存できない。
変更
- 設定変更と一時変更の2種類があり、一時変更にはさらに一時全体変更と、一時一部変更の2種類がある。
- 設定変更はMySQLサーバーとしての設定を変更する。
- 一時全体変更は、変更以後接続する場合に適用される。ただし、再起動するとリセットされる
- 一時一部変更は、今接続している自分自身にのみ適用される。PHPから使うのでこの設定を使うことは多分無い。
設定変更
- my.cnfを変更
my.cnf
[mysqld]
max_allowed_packet=16MB
- 反映には再起動が必要。
一時全体変更
set global max_allowed_packet = 10475520;
- 変更直後はSESSIONの値が適用されているので、一度接続しなおす必要がある。
一時一部変更
set max_allowed_packet = 10475520;
- 省略。
まとめ
- 運用環境で突然再起動なんてできないだろうし、かといって一生再起動しないというパターンもあり得ないので、結局set globalで一時的に変更して、再起動した時用にmy.cnfも修正する必要に迫られると思われる。
PHP(PDO)の設定
MYSQL_ATTR_MAX_BUFFER_SIZE
- PDOが読み出すバッファの最大サイズ。デフォルト1MB。
- 最大サイズを超えていてもエラーは出ない。
- 1MBを超えているファイルをダウンロードする場合にキッチリ1MBになっていたらこれが原因の可能性大。
変更
- 接続後の変更は不可で、PDOのコンストラクタに渡す必要がある。
new PDO("mysql:host=$host; dbname=$dbname", $user, $password,
array(PDO::MYSQL_ATTR_MAX_BUFFER_SIZE=>1024*1024*10));//バイトで指定
出力時の設定
- BLOBでファイルデータのみ格納している状態では適切なmime-typeを判別することは不可能なので、DB格納時に調べてファイル情報としてファイル名他と共に保存するのが良いと思う。
まとめ
-
ファイルは物理ファイルとして保存して、パス情報等のみ保存する方法は、SQLアンチパターン「ファントムファイル」としても挙げられているので、じゃあDBにファイルを保存しようとした結果がこの有様です。
-
実装さえ終われば、二度目以降は同じ失敗しないだろうし、トランザクション管理もバックアップもほぼ何も考えなくて良いので、今後もファイルは積極的にDBに持たせていきたい。