AndroidでSQLiteはどこまで書くものか


最近ちょっとしたきっかけがありまして、AndroidでSQLiteを触っていたときは、基本ロジックを入れなかったし、そういうサンプルなどもなかったなと思ったのですが、実際のところ、どういうバランスが良いのだろうか、と気になったのです。

というようなことをTwitterでつぶやいてみた所、大学時代のスパハカ先輩から、「重くなるからモバイル系では基本SQLはシンプルにするよ。」とのお言葉を賜りました。

じゃぁ、どの程度までが許されるのかなー、ということで、少し確認をしてみたのが、本稿です。

# 実行環境

* Android 4.3 (Nexus 7 (2012))

# 実験

CREATE TABLE時にフィールドにCHECK制約を入れてみる。

## テーブル

| フィールド名 | 型 | 制約 | 備考 |
|---|---|---|---|
| ID | INTEGER | primary key autoincrement | ただのID |
| VALUE_INT | INTEGER | - | int型のデータを受け取るだけ |
| VALUE_LONG | INTEGER | - | long型のデータを受け取るだけ |
| VALUE_BOOL | INTEGER | VALUE_BOOL IN (0, 1) | booleanとして1(true)とfalse(0)に制限 |
| STRING_8 |  TEXT | length(STRING_8) = 8 | 8 bytes 限定 |
| STRING_100 |  TEXT | length(STRING_100) = 100 | 100 bytes 限定 |
| STRING_1000 |  TEXT | length(STRING_1000) = 1000 | 1000 bytes 限定 |
| STRING_LT_100 |  TEXT | length(STRING_100) < 100 | 100 bytes 未満 |

## コード

### CHECK有りテーブル

``` java:SqlSampleData.java
public class SqlSampleData extends SampleData {
    public static final String CREATE_TABLE = ""
            + "create table " + TABLE_NAME + " ( "
            +           FIELD_ID                    + " integer primary key autoincrement "
            + ", " +    FIELD_VALUE_INT             + " integer " 
            + ", " +    FIELD_VALUE_LONG            + " integer "
            + ", " +    FIELD_VALUE_BOOL            + " integer "   + "check(" + FIELD_VALUE_BOOL + " IN (0, 1)) "
            + ", " +    FIELD_VALUE_STRING_8        + " text "      + "check(length(" + FIELD_VALUE_STRING_8 + ") = 8)"
            + ", " +    FIELD_VALUE_STRING_100      + " text "      + "check(length(" + FIELD_VALUE_STRING_100 + ") = 100)"
            + ", " +    FIELD_VALUE_STRING_1000     + " text "      + "check(length(" + FIELD_VALUE_STRING_1000 + ") = 1000)"
            + ", " +    FIELD_VALUE_STRING_LT_100   + " text "      + "check(length(" + FIELD_VALUE_STRING_LT_100 + ") < 100)"
            + " );";
}
```

### CHECK無しテーブル

``` java:CodeSampleData.java
public class CodeSampleData extends SampleData {
    public static final String CREATE_TABLE = ""
            + "create table " + TABLE_NAME + " ( "
            +           FIELD_ID                    + " integer primary key autoincrement "
            + ", " +    FIELD_VALUE_INT             + " integer "
            + ", " +    FIELD_VALUE_LONG            + " integer "
            + ", " +    FIELD_VALUE_BOOL            + " integer "
            + ", " +    FIELD_VALUE_STRING_8        + " text "
            + ", " +    FIELD_VALUE_STRING_100      + " text "
            + ", " +    FIELD_VALUE_STRING_1000     + " text "
            + ", " +    FIELD_VALUE_STRING_LT_100   + " text "
            + " );";
}
```

### 親クラス

``` java:SampleData.java
public class SampleData {
    public static final String TABLE_NAME = "tab";

    public static final String FIELD_ID = "id";
    public static final String FIELD_VALUE_INT = "VALUE_INT";
    public static final String FIELD_VALUE_LONG = "VALUE_LONG";
    public static final String FIELD_VALUE_BOOL = "VALUE_BOOL";
    public static final String FIELD_VALUE_STRING_8 = "STRING_8";
    public static final String FIELD_VALUE_STRING_100 = "STRING_100";
    public static final String FIELD_VALUE_STRING_1000 = "STRING_1000";
    public static final String FIELD_VALUE_STRING_LT_100 = "STRING_LT_100";
}
```

## 確認方法

それぞれのテーブルに対し、条件に適合するデータでINSERTを実行する。
INSERT回数を100回、1000回、10000回とし、それぞれ10度繰り返して平均時間を算出。

CHECK有り : CHECK有りテーブルを利用し、Java側での検証無し。
CHECK無し : CHECK無しテーブルを利用し、Java側で同等の検証有り。

以下、確認結果はミリ秒。

## 確認結果

### 100回

| 回数 | CHECK有り | CHECK無し |
|---|---|---|
| 1 | 71 | 91 |
| 2 | 108 | 82 |
| 3 | 71 | 56 |
| 4 | 66 | 77 |
| 5 | 102 | 81 |
| 6 | 69 | 57 |
| 7 | 97 | 91 |
| 8 | 99 | 82 |
| 9 | 66 | 59 |
| 10 | 77 | 85 |
| 平均 | 82.6 | 76.1 |

### 1000回

| 回数 | CHECK有り | CHECK無し |
|---|---|---|
| 1 | 531 | 624 |
| 2 | 505 | 509 |
| 3 | 539 | 476 |
| 4 | 537 | 454 |
| 5 | 531 | 455 |
| 6 | 535 | 510 |
| 7 | 550 | 505 |
| 8 | 538 | 484 |
| 9 | 547 | 454 |
| 10 | 505 | 474 |
| 平均 | 531.8 | 494.5 |

### 10000回

| 回数 | CHECK有り | CHECK無し |
|---|---|---|
| 1 | 4832 | 4814 |
| 2 | 4819 | 4473 |
| 3 | 4798 | 4563 |
| 4 | 4857 | 5030 |
| 5 | 4824 | 4421 |
| 6 | 5314 | 4303 |
| 7 | 5257 | 4614 |
| 8 | 4989 | 4446 |
| 9 | 4927 | 4545 |
| 10 | 4855 | 4306 |
| 平均 | 4947.2 | 4551.5 |

### まとめ

すべてにおいて、CHECK無しの方がCHECK有りの92%程度の時間となりました。
スパハカ先輩のお言葉通り、SQLに検証ロジックを入れると性能が落ちるのは確かなようです。

ただ、今回の検証では8%程度の劣化なので、扱うデータ量が少なく、一回のトランザクションで何レコードも扱わないのであれば、SQLに検証を入れていても問題にならないと思います。
少しの差ですが、記述するコード量も減るのではないかと。

一方、大量のデータを一度に扱う場合は、多少といっても差が大きくなってくるので、Java側での検証実装にしなければいけないでしょう。

後は、今回はあまりまじめにやっていないのですが、検証に失敗するデータがを扱う可能性が高い場合は、Java側の検証ではじいた方が性能は良くなるはずなので、そことの兼ね合いも出てくるのかな、と思います。