送信可能クエリ長制限について

現時点(Connector/J 5.0)において,送信可能なクエリの長さの最大値は"sign 2^32 = 2G"です.この長さは文字数ではなく,バイトサイズです.従ってマルチバイトの文字コードを使用している場合には,送れる文字数はそのマルチバイト分だけ除算されるものと思ってください.

Statementからの実行,PreparedStatement(クライアントサイド)からの実行のどちらであっても,Connector/Jは送信するクエリをBufferオブジェクトに一度格納します.

このBufferオブジェクトでは送信対象のクエリ文を送信に使う文字コードを用いてString.getBytes()で得られたbyte配列を格納しています.このbyte配列の最大長が"2^32 = 2G"となっているのがこの制限の直接的な理由です.これは配列のサイズはint型であるというJava言語の仕様とも関係があります.

一方,MySQL Client/Serverプロトコルにおける送信可能なクエリの長さの最大値は"unsign 2^32 = 4G"です.従ってMySQLそのものの制限よりもConnector/Jでの制限の方が厳しいということに成るわけですが,Connector/J 5.1においてもこの制限は変更しないことと成りました.

この決定が行われた理由は,実際に例えば3GBものクエリをConnector/JからMySQLへ送信するといったようなことがあった場合,クライアント側/サーバ側のそれぞれにおいてこの送受信のためだけにそれぞれ最低+3GBものメモリをallocすることとなるためです.こういった極めて長いクエリを実行する必要があるような場合には,ServerPreparedStatement(サーバサイド)を使用することができます.サーバサイドのPreparedStatementでは送信クエリを文字列としてではなく,ストリームで渡すことができます.またMySQL Client/Serverプロトコル上にこうした長さ制限はないようです.



、、、という訳で先週いろいろ勉強しましたがcom.mysql.jdbc.PreparedStatement.setNCharacterStream()ではsetNString()への委譲でOKだったみたいです.

例えば

CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 LONGTEXT, c3 LONGTEXT, c4 LONGTEXT)

こんなテーブルがあったとして,c2/c3/c4にそれぞれ1GBの文字列をPreparedStatement.setString()でセットしたとします.Connector/Jを呼び出しているJVMは64bitマシンで動いていて,100GBくらいのヒープが割り当てられているものとします.この場合,どれくらいヒープを消費するのかは分かりませんが,とりあえずsetString()までは動きます.java.lang.Stringの内部実装とか良くは知りませんが,Integer.MAX_VALUEよりも短い文字列であれば格納できるでしょう.

しかしPreparedStatement.execute()を呼び出すと現時点ではぶっ壊れる(どんなエラー/例外が起きるか予測不能)でしょう.上で書いているように,Connector/Jではcom.mysql.jdbc.Bufferオブジェクトに実行クエリをbyte化して格納します.このBufferオブジェクト構築時(fillSendPackesメソッドがexecuteメソッドにより呼ばれた時)に,確保すべきbyteのサイズを計算している際に,Integer.MAX_VALUEを超えてオーバーフローを起こしますが,ご存知の通りJavaではこのようなオーバーフローが発生しても何の例外もエラーもでません.今の例だと1GB * 3なので全部合わせて計算結果"-1G"とかになるのかもしれません.この場合はbyte[]をnewする際にNegativeArraySizeExceptionが発生するでしょうし,パラメータのサイズの組み合わせ次第では正の数となりそのタイミングでは例外が発生しないかもですがいずれSystem.arrayCopy()などが呼ばれた際にIndexOutOfBoundsExceptionなどが発生するでしょう.

つまり何が言いたいかというと,このクエリ長制限はPreparedStatement.setNCharacterStream(int, Reader, long)のようなlong型を許容するAPIが登場する以前から,普通にありましたねということです.そしてlong型のパラメータを受け取るAPIが出来ても"クライアントサイド・プリペーアドステートメント"に関しては今までと同じ制限がありますよ,ということです.

まあ1GBを超えるようなサイズのクエリをServerPreparedStatementを使わずに送る人が実際にいるのか?という実際的な疑問もあるわけですが,Connector/Jはいわばライブラリなのでその辺も考えとかなきゃいけないわけです.MySQL 4.0以下を使う場合にはServer-SideのPreparedStatement無いですしね.

ではそんな感じで・・・.