クイックスタート:5分でわかるSQL vs NoSQL
新しいプロジェクトを始める際に「MySQLとMongoDBはどっちを使う?」と聞かれたことはないでしょうか。この質問は多くのエンジニアが最初につまずくポイントです。まず詳細に入る前に、簡単にまとめておきましょう。
SQLデータベース(PostgreSQL、MySQL、SQLite)は、固定スキーマのテーブルにデータを格納します。スプレッドシートをイメージしてください。カラムは事前に定義され、すべての行が同じ構造に従い、テーブル間のリレーションシップが定義・強制されます。
NoSQLデータベース(MongoDB、Redis、Cassandra、DynamoDB)は、その厳格な構造を排除します。ドキュメント、キーバリューペア、グラフ、ワイドカラム——それぞれのNoSQLタイプが異なる特定の問題を解決します。
30秒でわかるチートシートはこちら:
- SQL:構造化データ、複雑なクエリ、ACIDトランザクション → ECサイト、銀行、CRM
- NoSQL:柔軟なスキーマ、大規模スケール、特定のアクセスパターン → SNSフィード、キャッシュ、リアルタイム分析
現実はどんなチートシートよりも複雑です。それぞれが実際に優れている場面を詳しく解説していきましょう。
詳細解説:実際に何が違うのか
スキーマ:厳格 vs 柔軟
SQLでは、最初の1行を書く前にスキーマを定義する必要があります。後からカラムを追加したい?ALTER TABLEマイグレーションを実行することになり、本番環境の大きなテーブルでは非常に大変です。
-- スキーマを事前に定義
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- 後からフィールドを追加する場合はマイグレーションが必要
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
MongoDBでは、ドキュメントごとに好きなフィールドを自由に追加できます:
// ドキュメントごとに異なるフィールドを持てる
db.users.insertMany([
{ email: "[email protected]", phone: "+1234567890" },
{ email: "[email protected]", preferences: { theme: "dark" } }
]);
プロジェクトの初期段階では、この柔軟性は本当のメリットです——要件は毎日変わり、毎日マイグレーションを書きたくはないでしょう。問題は後から表面化します。フィールドがすべて微妙に異なる5000万件のドキュメントをクエリしてみると、スキーマの規律がなぜ重要なのかすぐに理解できます。
リレーションシップ:JOIN vs 埋め込み
SQLはまさにこのために作られました。注文、顧客、商品、請求書といったエンティティ間の複雑なリレーションシップは、外部キーとJOINでクリーンにモデル化できます。
-- 関連テーブルをまたいでクエリ
SELECT
u.email,
COUNT(o.id) AS order_count,
SUM(o.total) AS total_spent
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.created_at >= '2024-01-01'
GROUP BY u.email
ORDER BY total_spent DESC;
MongoDBはこれを別の方法で処理します。関連データをドキュメント内に埋め込む(高速な読み取り、データの重複)か、外部キーに似たリファレンスを使用する——ただし手動で管理する必要があります。複雑なリレーショナルクエリでは、どちらのアプローチもSQLほどスッキリしていません。
スケーリング:垂直 vs 水平
従来のSQLデータベースは垂直スケーリングです——同じサーバーにCPUとRAMを追加します。限界に達するまではうまく機能しますが、コストが急速に増大します。
NoSQLデータベースは最初から水平スケーリングを念頭に設計されています:サーバーを追加し、データをシャーディングします。Cassandraが数百テラバイトのデータをどのようにさばくのか、DynamoDBがAWSインフラ上で毎秒数百万件のリクエストをどのように処理するのかはこれによります。
リードレプリカやCitusのようなツールを使った最新のPostgreSQLは、評判以上に水平スケーリングをうまく処理できます。SQLをスケーラブルでないと切り捨てないでください——ただ、スケールの仕方が違うだけです。
ACID vs 結果整合性
SQLデータベースはACID保証を提供します:
- 原子性(Atomicity):失敗したトランザクションは完全にロールバックされる——すべてか無か
- 一貫性(Consistency):データは常に定義したルールに一致する
- 独立性(Isolation):並行トランザクションが互いに干渉しない
- 永続性(Durability):コミットされたデータはクラッシュしても消えない
ほとんどのNoSQLデータベースは、パフォーマンスと可用性のために、これらの保証の一部をトレードオフします。技術的な用語は結果整合性——データは最終的には一致しますが、すぐではありません。ショッピングカートであれば問題ありません。銀行振込では——絶対にNGです。
応用:実際のユースケース
SQLが優れている場面
金融アプリケーション:銀行振込にはアトミックなトランザクションが必要です。書き込みの途中でサーバーがクラッシュしたために、2つの口座の間でお金が消えてしまうことは許されません。適切なトランザクション分離を持つPostgreSQLが正解です。
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
UPDATE accounts SET balance = balance + 500 WHERE id = 2;
COMMIT;
-- 何か失敗した場合、全体がロールバックされる
レポーティングと分析:複数テーブルにまたがる複雑な集計、ウィンドウ関数、CTE——SQLはこの分野で40年の蓄積があります。MetabaseやGrafanaのようなBIツールはネイティブにSQLを話すので、統合の手間を大幅に省けます。
スキーマが安定した成熟したプロダクト:データモデルが固まると、SQLの厳格さは資産になります。スキーマがドメインモデルのドキュメントとなり、データベースレベルで不正なデータの混入を防いでくれます。
NoSQLが優れている場面
コンテンツ管理とカタログ:商品ごとに属性がまったく異なる商品カタログは、MongoDBのドキュメントモデルにぴったりです。テレビには解像度とパネルタイプがあり、本にはISBNとページ数があります。これをSQLでモデル化すると、NULL可能なカラムだらけになるか、複雑なEAVパターンが必要になります。
// 構造がまったく異なる商品 — MongoDBでは問題なく動作する
db.products.insertMany([
{
type: "tv",
brand: "Sony",
resolution: "4K",
panel: "OLED",
size_inches: 55
},
{
type: "book",
title: "Clean Code",
author: "Robert Martin",
isbn: "978-0132350884",
pages: 431
}
]);
キャッシュとセッション:Redisがここでの標準です。サブミリ秒の読み取り、TTL有効期限、リストやソート済みセットのようなデータ構造。PostgreSQLの前段にRedisをキャッシュレイヤーとして配置するのは、私が経験した中で最もよく見る本番環境の構成の一つです。
# ユーザーセッションを24時間の有効期限でキャッシュ
redis-cli SET "session:abc123" '{"user_id": 42, "role": "admin"}' EX 86400
# 取得する
redis-cli GET "session:abc123"
高書き込みリアルタイムワークロード:IoTセンサーデータ、イベントログ、ユーザーアクティビティストリーム——CassandraとDynamoDBは毎秒数百万件の書き込みを難なくこなします。MySQLでこの量をさばくには、相当な追加エンジニアリングが必要になります。
ポリグロット・パーシステンス:両方を使う
本番環境での最善の答えは、しばしば「両方使う」です。ポリグロット・パーシステンスと呼ばれるこのアプローチは、1つのツールにすべてをやらせるのではなく、各タイプのデータをそれに最も適したデータベースに割り当てます。
典型的なECサイトのスタックは次のような構成になるかもしれません:
- PostgreSQL:注文、決済、在庫——リレーショナル、ACID必須
- MongoDB:商品カタログ——カテゴリごとの柔軟なスキーマ
- Redis:セッション、カート、レート制限——速度最優先
- Elasticsearch:商品検索——全文検索、ファセット
管理するコンポーネントは増えますが、設計されていない役割を強いられるものはありません。
実践的なアドバイス
まずPostgreSQLから始め、必要なら移行する
どんな新規プロジェクトにも私がデフォルトで勧めるのは:PostgreSQLから始めることです。信頼性が高く、優れたツールが揃っており、JSONカラムをネイティブにサポートし(これはSQL/NoSQLのギャップを大幅に縮小します)、ほとんどのプロジェクトが到達するより多くの負荷を処理できます。必要であれば後でRedisをキャッシュ用に追加するか、具体的なボトルネックに直面したときに特定のホットパスをNoSQLストアに移行しましょう。
データベースアーキテクチャの先走りは本当の罠です。100人以上のユーザーを獲得することのなかったアプリのために、Cassandraクラスターの設計に何週間も費やしたチームを何度も見てきました。
選択前にアクセスパターンを整理する
データベースを決める前に、主要な5つのクエリパターンを書き出しましょう。すべてが「IDでドキュメントを取得」や「ユーザーXのすべてのアイテムを取得」であれば、ドキュメントデータベースが理にかなっています。集計、レポート、マルチテーブルJOINならSQLを選びましょう。アクセスパターンが他の何よりも意思決定を左右します。
移行時はデータ形式に注意する
データベースシステム間を移動する際には、フォーマット変換が常に発生します——たとえばPostgreSQLからエクスポートしたCSVをMongoDBに取り込む場合などです。データインポート用にCSVをJSONに素早く変換する必要があるときは、toolcraft.app/ja/tools/data/csv-to-jsonを使っています。すべてブラウザ上で動作するので、データが外部に送信されることは一切ありません——顧客データを扱う際にはこれが非常に重要です。
NoSQLを永続的にスキーマレスとして扱わない
MongoDBがスキーマを強制しないからといって、スキーマを持つべきでないということにはなりません。アプリケーション層でバリデーションを追加するか、MongoDBの組み込みJSONスキーマバリデーションを使用しましょう。スキーマの無秩序な変化は、半年後には信頼性よくクエリできないコレクションを生む原因になります。
// コレクションレベルでスキーマバリデーションを強制する
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["email", "created_at"],
properties: {
email: { bsonType: "string" },
created_at: { bsonType: "date" }
}
}
}
});
可能な限りマネージドサービスを選ぶ
データベースをセルフホストする前に、クラウドプロバイダーが何を提供しているか確認しましょう。PostgreSQL/MySQL向けのRDS、DynamoDB、DocumentDB、ElastiCache——これらはバックアップ、フェイルオーバー、パッチ適用をすべて処理してくれます。Cassandraクラスターのセルフホストは実質的にフルタイムの仕事です。小規模プロジェクト向けにVPS上でPostgreSQLをセルフホストする?それは十分合理的です。コミットする前に運用コストを把握しておきましょう。
ここに普遍的な正解はありません。アクセスパターンと整合性の要件に基づいて選びましょう。実績のあるものから始める——PostgreSQLはほとんどのアプリに十分対応できます。複雑さを追加するのは、実際の測定可能なボトルネックに迫られたときだけにしましょう。部屋で最も派手なアーキテクチャ図は、たいてい構築すべきでないものです。

