世界でもっとも使われるデータベース、25年かけてデータ型チェックを習得

世界でもっとも使われるデータベース、25年かけてデータ型チェックを習得

SQLiteデータベース型安全STRICTエンジニアリング

データソース:HN + Evan Hahn blog + web research · HN

2021年11月27日、SQLiteはバージョン3.37.0をリリースした。性能が倍増したわけでも、目を見張る新機能が追加されたわけでもない。ただ、テーブル作成文の末尾に、開発者が一言 STRICT と付け加えられるようになっただけだ。

どういう意味か? 平たく言えばこうだ:この日から、SQLiteはついに一つのことを拒否できるようになった——「名前を電話番号の欄に入力する」という行為を。

この時点で、SQLiteの誕生からすでに21年が経過していた。そして、それはまさに、あなたのスマホのあらゆるアプリが使っている「見えない」データベースなのである。

SQLite 公式ロゴ——この軽量データベースエンジンは世界で1兆以上のアクティブなデータベースを駆動している。出典:sqlite.org

スマホの中にある、あなたの知らない「基盤」

まず、広く行き渡った誤解を一つ解いておこう。SQLiteはアプリストアからダウンロードできる「ソフトウェア」ではない。あなたのスマホに「SQLite」というアイコンは存在しない。これはデータベースエンジンであり——アプリの内部に潜み、静かにデータの保存と管理を行っている。

微信(WeChat)のチャット履歴、支付宝(Alipay)の取引明細、抖音(Douyin)の動画キャッシュ、スマホのアドレス帳、ブラウザが保存するパスワード、オフラインナビゲーションパッケージ……これらの背後には、すべてSQLiteが立っている。

信頼できる推定によれば、世界で1兆以上のSQLiteデータベースが同時に稼働している。この数字に近づけるデータベースは他に存在しない。それは紛うことなき「世界第一」である。

しかし、このチャンピオンには信じがたい「特技」がある:それは、保存されるデータの型が正しいかどうかをまったくチェックしないのだ。

「年齢に’田中’と入力? 問題ない、どうぞ」

「データ型をチェックしない」とはどういうことか? 筆者は身近なシナリオで例えてみよう。

あなたが銀行で口座を開設する。窓口係が用紙を差し出す。そこには「年齢」と「氏名」の欄がある。あなたは年齢欄に「田中」と書き、氏名欄に「42」と書く——正常なら、窓口係は用紙を差し戻すはずだ:「お客様、年齢は数字で、氏名は文字でお書きください。」

SQLiteのデフォルトモードでの動作は、窓口係が無表情でこう言うのに等しい:「構いませんよ。あなたが書いたものをそのまま受け取ります。年齢が’田中’?保存します。氏名が’42’?それでも結構です。あなたの自由です。」

コードで言い換えればこうだ:あなたはテーブルを作成し、「年齢」列は整数(INTEGER)、「氏名」列はテキスト(TEXT)と宣言する。その後、次のように実行する:

INSERT INTO ユーザー表 (年齢) VALUES ('私は数字ではありません');

MySQLやPostgreSQLといったデータベースでは、この文は即座にエラーになる。SQLiteでは? 実行成功。警告すらない。あなたのデータベースの「年齢」列には、以来「私は数字ではありません」というテキスト値が鎮座することになる。

これはエッジケースではない。2026年7月11日、開発者のEvan Hahnがブログ記事「Prefer STRICT tables in SQLite(SQLiteのSTRICTテーブルを優先しよう)」を公開し、Hacker Newsで約200ポイント、89のコメントを集めた。コメント欄には、開発者たちが「この罠にハマった」血涙の体験談が溢れた。

図1:STRICTモード vs 非STRICTモードの動作比較

STRICTモード比較:左がデフォルトモード(どんなデータでも受け入れる)、右がSTRICTモード(型が違えば即座に拒否)。出典:筆者がEvan HahnのブログおよびSQLite公式ドキュメントに基づき作成

操作非STRICT(デフォルト)STRICTモード
INTEGER 列に 'abc' を書き込む(テキストを数値列へ)✅ 受け入れ❌ エラー
INTEGER 列に '123' を書き込む(テキスト形式の数値で無損失変換可能)✅ 受け入れ✅ 受け入れ
列の型を GARBAGE と書く(スペルミス/架空の型)✅ 受け入れ❌ エラー
ANY 列に任意の型を書き込む✅ 受け入れ✅ 受け入れ
テーブル作成時に列の型を書かない✅ 受け入れ❌ エラー
許可される型無制限INT, INTEGER, REAL, TEXT, BLOB, ANY

20年続いた哲学戦争

この背後にあるのは、手抜きや怠慢ではない。SQLiteの創造者D. Richard Hippによる、深く考え抜かれた設計上の選択である。SQLiteの公式サイトには「フレキシブル型の利点」と題されたページ全体があり、型チェックをしないことの正当性を弁護している。

この選択の根源を理解するには、2000年に戻る必要がある。当時Hippは海軍の請負業者に勤務しており、艦艇上のシステム用に軽量なデータベースを必要としていた。市場に出ている選択肢は重すぎるか、サーバーを必要とするかのどちらかで——軍艦のような環境ではまったく非現実的だった。そこで彼は自ら一から書き始めた。

重要な影響源の一つはTCL——Hippが最も精通していたプログラミング言語だった。TCLは「動的型付け」言語であり、プログラマは事前に変数の型を宣言する必要がなく、あらゆるものを文字列として扱うことができる。Hippはこの哲学をSQLiteに持ち込んだ:列の型を宣言したか? 結構。しかしそれはあくまで「提案」だ。実際に何を保存するかは、あなたが決める。

その後20年にわたり、「フレキシブル型は機能かバグか」をめぐって、データベースコミュニティでは長きにわたる議論が繰り広げられた。

支持派(Hipp本人と彼のチーム)の核心的主張は3つ:

第一に、「私は35年間コードを書いてきたが、型チェックによって防がれたバグを見たことがない。」 Hippは公式ドキュメントで、TCLとSQLiteを数十年にわたって開発してきた中で、型制約の欠如に起因するプログラム障害を一度も思い出せないと述べている。彼の結論は、型チェックはCやC++のようなハードウェアに近い低レベル言語でのみ有用であり——すべてのデータを「値オブジェクト」として渡すSQLエンジンでは、型チェックはあまり役に立たないというものだ。

第二に、「型チェックで防げるのは、見つけやすい単純なミスだけだ。」 この論点はかなり鋭い:「氏名」を「年齢」に入れるような馬鹿げたミスは確かに防げる——しかしそれは簡単に見つかり、一度テストすればすぐに露呈する。本当にデバッグに3日かかるのは、「姓」と「名」を逆に入力した場合だ——どちらもテキストであり、型チェックはまったく気づかない。Hippは、型チェックが開発者に「データはもうきれいになった」という誤った安心感を与えると考える。

第三に、「柔軟性によって、他のデータベースではできないことができる。」 たとえば、一つのテーブルを任意の型のキーバリューストアとして使う、廃止された列を別の目的に再利用する、Excelからエクスポートした汚いCSVファイルをそのまま取り込んで後でクリーニングする——など。

反対派の反論も同様に強力だ:

「まさにその’見つけやすい’ミスが、百万行のデータの中で、あなたの見つけられない一本の針になるのだ。型チェックは決して、デバッグ中に捕捉できるバグを防ぐためのものではない——防ぐのは、本番環境の午前3時、ログに一切のエラーもなく、しかしユーザーデータがシステム的に破壊され始めるその瞬間だ。」

「35年間コードを書いて型バグを見たことがないだと? SQLite自体はCで書かれている。お前はそれをコンパイルするとき、Cの型チェックを享受しているんだ。厳格な型システムでSQLite自身の正しさを保証しておきながら、型チェックは他人には重要じゃないと言うのか?」

Hacker Newsのコメント欄で繰り返し引用された比喩がある:「これはUDPをTCPの代わりに使うようなものだ——速度とシンプルさのためにデータ検証を諦め、その後アプリケーション層で再送、順序付け、検証を手動で追加する。すべて追加し終わって気づく、君はより劣ったTCPを実装していただけだと。」

別のコメンテーターはさらに率直に言った:「パフォーマンスのためにデフォルトを調整するのは許容できる。しかし正当性のためにデフォルトを調整する——それは安心できない。」

STRICTモードは実際に何をするのか

2021年11月に話を戻そう。STRICT キーワードが行うことは、大きく分けて3つある:

一、型が一致しない書き込みを拒否する。 整数列にテキストを入れようとする?エラー。テキスト列に数字を入れようとする?許可——なぜなら数字は無損失でテキストに変換できるから。整数列に文字列 '123' を入れようとする?これも許可——'123' は完全に数値 123 に変換できるから。STRICT が見ているのは「値が無損失で変換できるかどうか」であり、表面的な型だけではない。この点では、多くの厳格な型データベースより賢いと言える。

二、架空のデータ型を拒否する。 非STRICTモードでは、テーブル作成時に列の型を GARBAGEDATETIMEJSONUUIDBLOBB などと書いても、SQLiteはすべてを受け入れ、黙って汎用型として扱う。STRICT モードでは、認められる型は6つだけ:INTINTEGERREALTEXTBLOBANY。うっかり BLOBBLOBB と打ち間違えたら?即座に指摘される。

三、柔軟性が必要な場合は ANY を使う。 STRICT は一律の制限ではない。ある列を ANY 型と宣言すれば、デフォルトモードと同じく任意のデータを受け入れる。違いは:柔軟性を許す場所をあなたが指定でき、デフォルトですべてが柔軟なわけではないということだ。

なぜ21年もかかったのか?

2000年から2021年まで、21年間。なぜこれほど基礎的なチェックメカニズムが、2世代のエンジニアのキャリアを跨いでようやく実装されたのか?

答えはSQLiteの核心的な約束に隠れている:後方互換性

SQLiteの開発者には、偏執的とも言える鉄の掟がある——あなたが今日書いたSQLiteのコードは、10年後にバージョンアップしても100%正常に動作しなければならない。つまり、デフォルトの動作は永遠に変えられない。変えれば、地球上で稼働しているあの1兆のSQLiteインスタンスに問題が生じる可能性があるのだ。

図2:SQLite型安全性の進化タイムライン

2000 ─ SQLite 1.0 リリース、フレキシブル型を中核設計哲学に

      │   「列の型は提案であって、制約ではない」

2009 ─ SQLite 3.6.19:外部キー制約の構文サポート
      │   ただしデフォルトは無効、手動で PRAGMA foreign_keys = ON が必要

      │   その後12年間、STRICTモードの提案は何度も議論されたが
      │   そのたびに「後方互換性」の鉄壁に阻まれた

2021 ─ SQLite 3.37.0:STRICTテーブル対応
      │   テーブル作成文の末尾に STRICT キーワードを追加、テーブル単位で選択
      │   グローバルスイッチはなし——依然として「自分で選べ」の哲学

2026 ─ Evan Hahn が記事を公開:「STRICTテーブルを優先的に使おう」
      │   HN 199ポイント、89コメント、議論はなお続く

3つのマイルストーンは21年にわたっており、毎回同じ原則に従っている:機能を追加することはできても、デフォルトの動作を変えることはできない。

これは孤立した事例ではない。外部キー制約——ユーザーを削除した後、注文テーブルに1万件の「所有者のいない注文」を残すのを防ぐ——についても、SQLiteは2009年に構文をサポートしたが、今なおデフォルトではオフになっている。データベース接続を開くたびに、手動で次の一文を実行しなければならない:

PRAGMA foreign_keys = ON;

そうして初めて外部キーチェックが有効になる。理由はまったく同じだ:デフォルト値を変更すれば後方互換性が壊れるからだ。

HNの議論では、ブラウザのようにデータベース作成時に COMPAT_MODE=2026 を宣言し、新しいバージョンが自動的に当時の推奨設定を適用するという妥協案が提案された。しかしこの提案は今のところ採用されていない。

あるコメンテーターはこう書いた:「SQLiteはデフォルト値をめったに変更しない。なぜなら後方互換性の約束がほとんど神聖なものだからだ。SQLite 3.53向けに書かれたソフトウェアが、3.54にアップグレードしたとたんに CREATE TABLE が突然STRICTになってすべて壊れる——開発者はそんな事態を望んでいない。」

この言葉は、SQLiteのジレンマを正確に言い当てている:一方には「絶えず良くなろう」とする進化の衝動、もう一方には「絶対に変わらない」という互換性の誓い。

SQLiteの成功は、「何も管理しない」ことにある

ここまで読んで、直感に反する疑問が自然と浮かぶだろう:SQLiteにはこれほど多くの「デフォルトで安全ではない」設計があるのに、なぜそれが世界で最も使われるデータベースなのか?

答えはその設計哲学に隠れている。SQLiteの成功は、その「何も管理しない」姿勢に大きく依存している。

インストール不要、サーバー不要、設定ファイル不要。数百KBのライブラリファイルをアプリに埋め込めば動作する。データ型を管理しない——何でも好きに保存すればいい。外部キー関係を管理しない——問題が起きたら自分で責任を取れ。トランザクション分離レベルを管理しない——とにかく先に動かせ。

このミニマリズムの果実は:SQLiteをスマホ、ブラウザ、IoTセンサー、ルーター、スマートTV、車載システム、航空機エンターテインメントシステムに詰め込むとき、それは環境を選ばず、リソースを要求せず、起動に失敗しない。

あたかも万能コンセントのように——どんなプラグでも突き刺せる。ショートするかどうかは、私の管轄外。

STRICT モードの登場は、この「何も管理しない」で21年やってきたデータベースが、ついに一つの現実を認めたことを意味する:ユーザーが数十人のプロのCプログラマから、スキルレベルにばらつきのある世界中の何百万ものアプリ開発者に膨れ上がったとき、「デフォルトの自由」は「デフォルトのリスク」になりつつあるのだ。

エピローグ

SQLiteのこの歴史は、ソフトウェア工学全体の座標系で見れば、産業全体が徐々に成熟してきた縮図である。

初期のソフトウェアは少数の専門ユーザーを対象とし、その設計哲学は「最大の自由を与える。問題が起きればそれは自分の責任」だった。今日のソフトウェアは数十億の一般ユーザーを対象とし、設計の重心は「自由」から「安全性」と「フールプルーフ」へと移行している。

STRICT モードは、胸の高鳴るような技術的ブレークスルーではない——そのやっていることは、MySQLやPostgreSQLが誕生初日からできていたことだ。しかしそれが21年遅れたこと自体が、静かに一つの事実を物語っている:今日私たちが当たり前だと思っている「基本機能」の多くは、10年、20年にわたる業界の蓄積、議論、試行錯誤、そして軌道修正によって——少しずつ獲得されてきたのである。

次にあなたのスマホアプリが静かにSQLiteにデータを保存するとき、考えてみてほしい:あなたのスマホで何千日も休みなく働き続けてきたこの見えないチャンピオンは、その人生の21年目にしてようやく、幼稚園児でも身につけているスキルを学んだのだ——

靴を茶碗に入れてはいけない、ということを。


参考リンク: