FAQ

[ナレッジに戻る]
1. スキーマ関連
1.1 表の一覧を見ることはできますか
1.3 ステミングさせたいのですが
2. 検索操作関連
2.1 LIKEのエスケープ文字について教えてください
2.2 SELECT文でORDER BYを指定しないときはどういう順序になりますか
2.3 SELECT文でORDER BYがあるときORDER BYに指定されない列の順序はどうなりますか
2.4 文字列の並び順はどうなっていますか
2.5 NULLに対する大小関係はどうなっていますか
3. チューニング関連
3.1 十分な性能が出ないのですが
3.2 ロックの競合が起きるのですが
3.3 一括挿入が遅いのですが
3.4 英文のみ入っている列の性能が出ないのですが
4. ログ関連
4.1 [ERR] Database 'データベース名' is not available
4.2 [INFO] Checkpoint occured が長時間出力されないのですが
4.3 [INFO] Start FullText Index Merge のEndが出力されないのですが
5. その他
5.1 Exception::Object でキャッチできない例外があるのですが
5.2 バックアップをしたいのですが
5.3 DoqueDBのパラメータを設定したいのですが
5.4 サロゲートペアを扱えますか

1. スキーマ関連

1.1 表の一覧を見ることはできますか

DoqueDBではスキーマ情報を見るための特殊な組み込み表が用意されています。 以下に表定義と索引定義の組み込み表を示します。

-- 表定義の組み込み表
create table SYSTEM_TABLE
(
    RowID    int,           -- 表ID
    Name     nvarchar(50),  -- 表名 
    AreaID   int array[7]   -- 表を格納するエリアID
);

-- 索引定義の組み込み表
create table SYSTEM_INDEX
(
    RowID    int,           -- 索引ID
    ParentID int,           -- 索引が属する表のID
    Name     nvarchar(50),  -- 索引名 
    Type     int,           -- 索引の種別(1:B+木索引, 2:全文索引, 3:ビットマップ索引)
    Flag     int,           -- 索引のプロパティ(奇数:unique, 偶数:非unique)
    FileID   int,           -- 索引ファイルのID
    AreaID   int,           -- 索引を格納するエリアID
    HintString nvarchar(no limit)   -- 索引のヒントに指定された文字列
);

また、組み込みデータベース「$$SystemDB」には、以下のようなデータベース定義の組み込み表が用意されています。

-- データベース定義(組み込みDB:$$SystemDBでのみ定義)
create table SYSTEM_DATABASE
(
    RowID    int,           -- データベースID
    Name     nvarchar(50),  -- データベース名 
    Flag     int,           -- データベースのオプション
    Paths    nvarchar(100) ARRAY[3]
             -- データベースパス([1]:データパス, [2]:論理ログパス, [3]:システムパス)
);

これらの表は通常の表と同様にSELECT文の一部に使用できます。 更新操作は行えません。

以下は「TBL」で始まる名前を持つ表に属するすべての索引の名前を得るSQL文です。

select name from system_index where exists
    (select * from system_table
         where name like 'TBL%'
         and system_table.rowid = system_index.parentid);

1.2 ステミングさせたいのですが

全文索引のHINT句でstemming=trueと指定してください。

2. 検索操作関連

2.1 LIKEのエスケープ文字について教えてください

DoqueDBは、全文索引の有無に関わらずLIKE述語により文字列マッチを行うことができます。 構文は以下のようになっています。

select * from TBL where f like <パターン文字列> escape <エスケープ文字>;

エスケープ文字はパターン文字列中の特殊文字をその文字自体として処理させるために使います。 特殊文字とは以下のものです。

%                0文字以上の任意の文字にマッチ
_ (下線)         1文字の任意の文字にマッチ
エスケープ文字

これらの特殊文字の前にエスケープ文字を置くと、特殊文字がその文字自体として処理されます。

なお、シングルクオートをエスケープ文字の直後においてもエスケープされません。 通常の文字列リテラルと同様に、文字列中のシングルクオートは2つ重ねる必要があります。

escape以下を省略したときは、エスケープ文字はないものとして処理されます。 エスケープ文字には1文字であれば任意の文字を指定できます。 ただし、'%'や'_'をエスケープ文字にするとパターン文字列を書くときに混乱するので避けたほうがよいでしょう。

以下に例を書きます。

f like '100%'               -- '100'、'1000'、'100%'、'100人力'などにマッチ
f like '100_'               -- '1000'、'100人'などにマッチ
f like '100*%'              -- '100*3=300'、'100*****'などにマッチ
f like '100*%' escape '*'   -- '100%'にのみマッチ
f like '100**'              -- '100**'にのみマッチ
f like '100**' escape '*'   -- '100*'にのみマッチ

2.2 SELECT文でORDER BYを指定しないときはどういう順序になりますか

端的に答えると「不明」です。 レコードの取得順が重要な場合はORDER BY句を使用してください。

ORDER BYがないときのレコードの取得順序は更新操作の手順やSELECT文の形により変わるため、内部の構造をある程度知らないと予測できません。 それでもどういう順序になるのか知りたい人のために、下記にいくつかの場合について列挙します。

表がひとつで索引を使える条件がない場合
このとき、レコードはRecordファイルの先頭から順に取得されます。 そうすると、登録した順なのではないかと思われるかもしれません。 たしかに表を定義してから登録または更新しかしていないなら、そのとおりです。
しかし、削除操作があるとそうとはいえません。 DoqueDBでは、Recordファイルから削除された領域は再利用可能な領域としてフリーリストで管理されます。 次に登録操作があるとフリーリストから再利用可能な領域を取り出し、そこにデータを登録します。 フリーリストはLIFOで管理されます。 つまり、最後に削除された領域から再利用されます。
したがって、たとえば表を定義してから3件のレコードを登録し、そのすべてを登録した順に削除し、次に新たな3件のレコードを登録すると、ORDER BYなしで取得したときの順序は登録の逆順になります。 さらに続いてレコードを登録していくと、その後は登録順になります。
SQL> create table TBL (f int);
SQL> insert TBL values (1), (2), (3);
SQL> select f from TBL;               -- 登録した順になる
{1}
{2}
{3}
SQL> delete TBL;                      -- 登録した順に削除される
SQL> insert TBL values (4), (5), (6);
SQL> select f from TBL;               -- 登録と逆順になる
{6}
{5}
{4}
SQL> insert TBL values (7), (8), (9);
SQL> select f from TBL;               -- さらに登録したぶんは登録順になる
{6}
{5}
{4}
{7}
{8}
{9}
表がひとつでBtree索引が使える条件がひとつの場合
このときは索引ファイルを走査するので、索引に用いられているキーの昇順になります。 同じキーの中では登録順です。 削除操作の有無には関係ありません。
SQL> create table TBL (f int, g int);
SQL> create index BIDX on TBL(f);
SQL> insert TBL values (1, 1), (2, 22), (1, 11), (2, 2);
SQL> select f, g from TBL where f > 0;  -- fの順になる。fが同じなら登録順
{1,1}
{1,11}
{2,22}
{2,2}
表がひとつで全文索引が使える条件がひとつの場合
全文索引はROWIDの順に結果を返します。 ROWIDは登録ごとに1ずつ増える数値なので、登録順ということになります。 削除操作の有無には関係ありません。
SQL> create table TBL (f int, t nvarchar(100));
SQL> create fulltext index FIDX on TBL(t);
SQL> insert TBL values (1, 'abc'), (2, 'aabbcc'), (1, 'cab'), (2, 'ccaabb');
SQL> select f, t from TBL where t like '%ab%';  -- 登録順になる
{1,abc}
{2,aabbcc}
{1,cab}
{2,ccaabb}
表がひとつで索引が使える条件が複数の場合
索引を複数使う場合、一般には登録順になります。 これは、複数の索引ファイルからの結果をマージするのにROWIDの順に並べ直しているからです。
SQL> create table TBL (f int, t nvarchar(100));
SQL> create index BIDX on TBL(f);
SQL> create fulltext index FIDX on TBL(t);
SQL> insert TBL values (1, 'abc'), (2, 'aabbcc'), (1, 'cab'), (2, 'ccaabb');
SQL> select f, t from TBL where f > 0 and t like '%ab%';  -- 登録順になる
{1,abc}
{2,aabbcc}
{1,cab}
{2,ccaabb}
ただし、SELECT句にある列がすべてひとつのBtree索引のキー列だった場合は、そのキーの昇順になります。 これは、SELECT句に対応した列の値をBtree索引から順次取得しながら、残りの索引と結果を照合するという処理になるからです。
SQL> create table TBL (f int, t nvarchar(100));
SQL> create index BIDX on TBL(f);
SQL> create fulltext index FIDX on TBL(t);
SQL> insert TBL values (1, 'abc'), (2, 'aabbcc'), (1, 'cab'), (2, 'ccaabb');
SQL> select f from TBL where f > 0 and t like '%ab%';  -- f順になる
{1}
{1}
{2}
{2}
表が複数の場合
FROM句に複数の表がある場合、結合がどう処理されるかによって取得順が変わります。
結合条件が索引で処理されるなら、最初に取得される表は上記の表がひとつの場合と同様に考え、次に取得される表は結合条件の処理に使われる列の値が同じものの中では登録順になります。
SQL> create table TBL1 (f int, g int);
SQL> create table TBL2 (h int, t nvarchar(100));
SQL> create index BIDX2 on TBL2(h);
SQL> insert TBL1 values (1, 100), (3, 300), (2, 200);
SQL> insert TBL2 values (1, 'abc'), (1, 'def'), (2, 'xyz'), (3, 'uvw');
SQL> select * from TBL1, TBL2 where f = h;  -- TBL1→TBL2の順に取得される
{1,100,1,abc}                                  -- TBL1は条件がないので登録順
{1,100,1,def}                                  -- TBL2はキーが同じなら登録順
{3,300,3,uvw}
{2,200,2,xyz}

2.3 SELECT文でORDER BYがあるときORDER BYに指定されない列の順序はどうなりますか

ORDER BYが指定されているときはもちろんORDER BYに指定された列の順で取得されますが、その列の値が同じものの間での並び順は不明です。

ORDER BYがBtree索引で処理できる場合
ORDER BYに指定された列にBtree索引がついていて、以下の条件のいずれかを満たす場合は、ORDER BYをBtree索引で処理、つまりBtree索引を走査することでORDER BYで指定された順に取得するという動作をします。
この場合、キーが同じものに対しては登録順になります。 ただし、ORDER BYにDESCが指定されているときは索引を逆順に走査するので、キーが同じものに対して登録の逆順になります。
ORDER BYをメモリー上で処理する場合
Btree索引を使えない場合、ORDER BYで指定された順にレコードを並べ替える処理はメモリー上で行われます。 この並べ替えはクイックソートアルゴリズムで行われます。
クイックソートへの入力は上記ORDER BYがないときの順序になりますが、クイックソートは入力の順序を保存しないアルゴリズムなので、結果の順序について一般的な予測をすることはできません。

2.4 文字列の並び順はどうなっていますか

ORDER BYに文字列が指定された場合、Unicode順になります。 他の順序(たとえばShiftJIS順)にすることはできません。

2.5 NULLに対する大小関係はどうなっていますか

ORDER BYによる並び替えで、NULLはもっとも小さい値として扱われます。

ちなみに、比較演算ではNULLとの比較はUnknownなので NULL < 10 はヒットしません。 並び替えでのみそのような扱いになります。

3. チューニング関連

3.1 十分な性能が出ないのですが

期待に反して十分な性能が出ないとしたら、いろいろな原因が考えられます。

状況を調べる

まずはどのようなSQL文の発行にどのくらいの時間がかかっているのか調べてみましょう。

DoqueDBに渡されているSQL文とその実行にかかる時間は、パラメーター設定によりファイルに出力することができます。 <インストールパス>/etc/default.confに以下のキーを追加します。

Statement_KeepSQLStatement        "true"
Server_PrintSQLStatement          "ファイル名"
Server_PrintParameter             "ファイル名"
Server_PrintTime                  "ファイル名"

このように設定したのちにサーバーを起動すると、サーバーに対して投げられるすべてのSQL文と実行時間が、「ファイル名」で指定されたファイルに以下のように出力されます。

(872:2080) Server::worker.cpp 857: [DEBUG] SQL: select f from TBL where g >?
(872:2080) Server::worker.cpp 883: [DEBUG] Parameter: {100}
(872:2080) Server::worker.cpp 266: [DEBUG] Optimizer Time: 00:00:00.050
(872:2080) Server::worker.cpp 266: [DEBUG] Executor Time: 00:00:00.031
(872:2080) Server::worker.cpp 266: [DEBUG] Worker Time: 00:00:00.094

ログの見方は以下の通りです。

(872:2080)
プロセスIDとスレッドIDの組です。 ログ上でセッションを区別するのに用いることができます。
Server::Worker.cpp 857
ログを出力しているソースファイルと行番号です。 特にユーザーが気にすることはありません。
[DEBUG]
動作解析用に出力ているログであることを示します。
SQL: ...
発行しているSQL文です。
Paremeter:
SQL文で「?」により指定されたパラメーターに与えるデータです。
Optimizer Time:
SQL文の解析および最適化処理にかかった時間です。 サーバー起動後初めてデータベースにアクセスするような場合、システム表の読み込みのために数百msの時間がかかることがあります。
Executor Time:
SQL文の実行にかかった時間です。 最適化処理の終了から最後のデータをクライアントに返すまでの時間です。
Worker Time:
その他のオーバーヘッドも含んだすべての実行時間です。 解析にはこの時間を使用します。

このようにして出力したログをチェックすることで、時間のかかるSQL文を特定することができます。 もし全体的に遅いのであれば環境や設定をチェックすることになります。

注意:上記の設定はオーバーヘッドになるので実運用では削除してください。

以降で、主な原因のパターンごとに少し詳しく述べていきます。

スキーマ定義に工夫の余地がある
表や索引の定義方法と実際のアクセス方法が合っていないと十分な性能が出ない場合があります。
スキーマ定義の説明に解決策があるかもしれません。
SQL文に工夫の余地がある
一般的なDBアプリと同様、発行するSQL文の数を減らすことが有効です。
何度も同じ検索をしていないか、複数のSQL文で得られるのと同じ結果がひとつのSQL文で実現できないかといった点について検討してみてください。
毎回JOINする表があったら、それらをまとめてひとつの表にしてしまうなど、スキーマ定義との組み合わせで改善できることもあります。
扱うデータ量に比べてバッファサイズが小さい
DoqueDBのデフォルトバッファサイズは20MBです。 データベース全体のサイズが数百MBから数GBもある場合は、このバッファサイズではファイルI/Oが頻発して速度が著しく低下します。
インストール時にconf.shを編集してバッファサイズを大きくすることができます。 また、インストールしてしまったあとでも、パラメーターファイルを編集することもできます。
※128Mバイトにする場合:
    <インストールパス>/etc/default.conf に以下の行を追加
        Buffer_NormalPoolSize         134217728
    または
        Buffer_NormalPoolSize         "128M"
RAIDのコンフィグレーションがDoqueDBに不向き
RAIDを使用している場合、RAIDコントローラーの設定により数倍~数十倍の性能差が出ます。 これは版管理の仕組み上ランダムアクセスが多くなるという、DoqueDBのファイルアクセスの特性によるものと考えられます。
古いデータになりますが、RAID上のベンチマーク結果を記載します。
マシン環境
サーバ: DELL PowerEdge2850
    CPU: Xeon3.2GHz x 2 (Hyper Threading ON)
    RAM: 4096MB
    OS: Windows Server 2003 Standard Edition
    RAID:
    RAID5 (146G x 4)
    コントローラー: PERC 4e/DI
    メモリー: 256MB
    ストライプサイズ: 32KB
    Read Policy: Adaptive Read-Ahead | No Adeah
    Write Policy: Write-Back | Write-Through
    Cache Policy: Direct I/O | Cache I/O

10万件(平均20KB)のテキストデータ登録時間
Read Policy             Write Policy    Cache Policy    実行時間        うちWrite時間   うちRead時間
--------------------------------------------------------------------------------------------------------------------
Adaptive Read-Ahead     Write-Back      Cache I/O       2171秒          97秒            207秒
Adaptive Read-Ahead     Write-Back      Direct I/O      2098秒         101秒            227秒
No Ahead                Write-Through   Direct I/O      6091秒        4272秒            326秒

3.2 ロックの競合が起きるのですが

多くのセッションから並行して使用される表に索引をつけることで、競合を起きにくくすることができます。

たとえば、列Cがある条件を満たすレコードを削除する場合を考えます。 列 C に索引が付いていなければ、削除の処理は全レコードスキャン&条件判定という処理手順になります。 この処理手順のときは全レコードに対して1レコードずつ順番にロックがかけられ、条件判定(さらにTRUEなら削除)後、ロックが外されるという処理になります。 したがって、各セッションの列Cに対する条件が異なっていてもロックの競合が起こる可能性があります。

そこで、列Cに索引を定義すると、直接条件を満たすレコードのみが取得されるため、削除されるレコードだけがロックされることになり、競合が起きにくくなります。

ただし、スキーマ定義の説明にあるように、不要な索引は性能を低下させる恐れがありますので注意してください。

3.3 一括挿入が遅いのですが

検討できる対策がいくつかあります。 以下個別に見ていきます。

実行状況を見直す
挿入している間は1クライアントしか使わないことが分かっているでしょうか? もしそうであれば、挿入部分を以下のトランザクションで囲むとロック情報の管理が減るので速くなります。
START TRANSACTION READ WRITE, ISOLATION LEVEL SERIALIZABLE
(挿入処理)
COMMIT
挿入する表に索引が付いているでしょうか? もし付いているのであれば、挿入前に索引をいった削除して、挿入後に再作成した方が速いかもしれません。 新規に挿入する件数が挿入後の全体の件数に対して占める割合が大きいときに有効です。
クライアントの使い方に適切ではないところはありませんか? 一般的なことも含みますが、以下の内容を確認してみてください。
ASCII    ... ModKanjiCode::unknown
ShiftJIS ... ModKanjiCode::shiftJis
UTF-8    ... ModKanjiCode::utf8
スキーマとデータと操作を確認する
そのスキーマは、挿入データやそのデータに対する操作に対して適切に定義されているでしょうか? その特性が合っていないと十分な性能が出ない場合があります。
たとえば、ビットマップ索引は、異なり数の少ないデータ(逆に言うと同じ値を持つレコードが多いデータ)に対して有利な索引です。 そのため、異なり数が100程度であれば問題ない速度で挿入できますが、それ以上になると著しく性能が低下する可能性があります。
他には、全件DELETEしてからINSERTするという処理もビットマップ索引には向いていない操作になります。 差分だけ更新できるならそうしてください。 そうできない場合は、状況にもよりますがDROP TABLEしてCREATE TABLEした方がよい場合もあります。
その他の注意点については、スキーマ定義の説明に解決策があるかもしれません。
SQL文を工夫する
複数のINSERT文をまとめることで速くなります。 以下のような書き方です。
INSERT INTO T1 VALUES
                  ('aaa', 10, 'zzz', ..., 100),
                  ('bbb', 20, 'yyy', ..., 200),
                  ...
                  ('zzz', 270, 'aaa', ..., 2700);
加えて、以下のようなSQL文をPrepareして使いまわすことでさらに速くなります。
INSERT INTO T1 VALUES
                  (?,?, ...,?),
                  (?,?, ...,?),
                  ...
                  (?,?, ...,?);
たとえば1000件ずつ挿入する場合に、挿入する件数が1000で割り切れない場合は、余りの分だけ別のSQL文にするといいでしょう。
一度に挿入するのに最適な件数ですが、一般的な解はありません。 実際のデータで、100件の場合、1000件の場合など実験していただくのが確実だと思います。
というのも、一度に挿入する数が多くなると、サーバークライアント間の通信オーバーヘッドやSQL文ごとの処理オーバーヘッドは少なくなりますが、多くのメモリーを消費することになるため、メモリー確保が遅くなっていきます。 このトレードオフの最適値は、表を構成する列の数や型、実際のデータ、バッファサイズに依存しているからです。
ログ無しモードで実行する
論理ログおよび物理ログを取らないモードにして実行すると速くなります。 ただし、エラーが起きてもロールバックはできませんし、途中で電源断などで落ちたらデータベースが壊れることになりますので、最悪の場合いちから作り直してもいいケースに限定されます。
方法は以下の通りです。
  1. <インストールパス>/etc/default.confに以下のキーを追加する。
    Trans_NoLogicalLog  "true"
    Version_NoVersion   "true"
    
  2. サービスを再起動する。
  3. 一括insertを実施する。
  4. 上記のパラメーターを両方falseにするか、削除する。
  5. サービスを再起動する。
バッチインサートを利用する
ファイル上のデータを一括挿入する、バッチインサートという手段が用意されています。 実行方法についてはバッチインサートを参照してください。

3.4 英文のみ入っている列の性能が出ないのですが

どんな索引を使っているでしょうか?

もし索引に uni-gramを使っているのでしたら、変更した方がいいでしょう。 データサイズが大きくなるようなら性能がまったく出ないおそれもあります。 特別な理由がなければindexing=wordをお勧めします。

網羅性が気になるのでしたら、これにstemming=trueを付けるといいでしょう。

4. ログ関連

4.1 [ERR] Database 'データベース名' is not available

自動復旧について3.1 Not availableにあるように、そのデータベースは一切の操作を受け付けない状態になっています。 しかし、そのデータベースは壊れておらず、データ保護のために利用制限しているだけの場合もあります。

たとえば、下記のようなログが事前に出ていた場合です。

[INFO] (Checkpoint) Database synchronization failed.

このログは、不正なメモリーを参照したために出力される場合があるので、利用制限しているだけかもしれません。 サービスを再起動してみてください。

ちなみに、この「不正なメモリーを参照」する原因ですが、データベース全体のデータとデータ操作のすべてのログがあれば、解明できる可能性は高くなります。 しかし、データ操作のログの量は下手をすると数百GBになるかもしれませんし、動作速度も低下します。

4.2 [INFO] Checkpoint occurred が長時間出力されないのですが

このログは、自動復旧についてにあるように、定期ジョブで実行されているので、通常は定期的に出力されます。 ただし、SQL文が実行されているあいだはこのログは出力されません。 したがって、このログが出ていないことから、SQL文の実行に時間がかかっていることは推測できますが、何らかの障害が起きていることを示せるわけではありません。

4.3 [INFO] Start FullText Index Merge のEndが出力されないのですが

このログは、全文索引の小転置ファイルを大転置ファイルにマージする処理が起動したことを示しています。 これは、小転置の大きさがある程度大きくなった場合に起動されます。 完了するまでのあいだ、ほかの操作の割り込みは可能なので、状況によっては時間がかかります。 したがって、Endのログが出ないことから、全文索引の更新処理に非常に時間がかかっていることは推測できますが、何らかの障害が起きていることを示せるわけではありません。

しかし、たとえば6時間もマージ処理が実行しっぱなしということになると、通常では考えられないため、何らかの障害が起きている可能性があります。

5. その他

5.1 Exception::Object でキャッチできない例外があるのですが

DoqueDBには2つの例外クラスがあります。

ほとんどの場合、Exception::Objectの例外がスローされますが、メモリー確保に失敗した場合などには、下位ライブラリであるlibcommonからModExceptionがスローされる可能性があります。

5.2 バックアップをしたいのですが

オンラインバックアップとオフラインバックアップをサポートしています。 詳細はユーザーズマニュアルをご覧ください。 サービスを停止しているあいだにファイルを直接コピーすることも可能です。

5.3 DoqueDBのパラメータを設定したいのですが

サービス起動時の設定が使われます。

たとえばパラメータの設定とバックアップとを同時に実行したいときは、以下のような手順になります。

  1. サービスを停止する。
  2. データベースをコピーする。
  3. パラメータを設定する。
  4. サービスを起動する。

5.4 サロゲートペアを扱えますか

サロゲートペアは、16ビットに収まらないUnicode文字を16ビット2文字で表現する仕組みです。 たとえば上が「土」になった「吉」は、Unicodeではサロゲートペア文字になります。

DoqueDBでは、入出力の文字コードをUTF-8とすることで、 サロゲートペアを格納し、検索し、取り出すことができます。 ただし、サロゲートペアは内部的には2文字として扱われるため、 たとえばSUBSTRING関数でサロゲートペアの一方が切り出されることがあり、 またLIKE述語では連続する2個の下線(_)とマッチします。 サロゲートペアの断片は、UTF-8で出力するときに置換文字 (U+FFFD, UTF-8では0xEF 0xBF 0xBD)に変換されます。


Copyright (c) 2023 Ricoh Company, Ltd. All rights reserved.