首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

資訊發布系統彈性資料模型設計:EAV、JSON、多表三大方案比較

     編輯:PHP 相關知識

問題情境

資訊發布系統中,不同分類 (新聞、產品、活動、下載) 所需欄位差異大:共同欄位 (標題、發布時間、內文) 之外,產品需規格/價格/圖片,活動需地點/報名截止/人數,下載需檔案/版本/相容性。如何設計資料庫結構兼顧彈性、查詢效能、維護成本?

方案比較總覽

方案核心思想優點缺點適用場景
單大表 (Wide Table) 所有可能欄位建在一張表 查詢簡單 (單表)、無 Join、開發直觀 大量 NULL、DDL 變更鎖表、稀疏欄位浪費空間、超過欄位數限制 分類少 (<5)、欄位差異小、原型快速開發
核心表 + 延伸表 (Class Table Inheritance) 共同欄位放 content,各分類建 content_product, content_event 等 1:1 延伸表 結構正規化、型別嚴格、查詢可 Join、擴充不影響核心 查詢需 Join/Union、新增分類需建表+修改代碼、異構查詢複雜 分類穩定、欄位型別固定、需嚴格資料完整性
EAV (Entity-Attribute-Value) attributes 定義欄位中繼,values 儲存實際值 (長表) 極度彈性、無需 DDL、動態新增欄位 查詢極慢 (多次 Self-Join)、型別驗證困難、索引難建、SQL 複雜 欄位極度動態、使用者自訂欄位、表單產生器
JSON/JSONB 欄位 (PostgreSQL/MySQL 5.7+) 核心表含 extra_data JSON,分類差異欄位存入 JSON 結構與彈性平衡、單表查詢、支援索引 (GIN/虛擬欄位)、無 DDL 型別約束弱、跨資料庫遷移麻煩、舊版 MySQL 不支援 現代首選:分類多、欄位半結構化、需快速迭代
文件導向 (MongoDB/Elasticsearch) 每筆文件結構獨立,Schema-less 天然適配異構資料、水平擴展、全文搜尋強 交易支援弱、運維成本高、團隊技術棧擴大 微服務架構、大量非結構化內容、需強大搜尋

推薦方案:核心表 + JSON 擴充欄位 (MySQL 5.7+/PostgreSQL)

資料表結構

CREATE TABLE `contents` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `type` VARCHAR(30) NOT NULL COMMENT 'news/product/event/download',
    `title` VARCHAR(255) NOT NULL,
    `body` LONGTEXT NOT NULL,
    `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1=發布 0=草稿 -1=刪除',
    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `published_at` DATETIME NULL,
    `extra` JSON NOT NULL COMMENT '分類專屬欄位',
    PRIMARY KEY (`id`),
    INDEX `idx_type_status` (`type`, `status`),
    INDEX `idx_published` (`published_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

各分類 JSON 結構範例

-- 產品
{
  "price": 1299,
  "currency": "TWD",
  "specs": {"CPU": "i7", "RAM": "16GB", "SSD": "512GB"},
  "images": ["prod1.jpg", "prod2.jpg"],
  "sku": "NB-2024-001"
}

-- 活動
{
  "venue": "台北國際會議中心",
  "start_at": "2024-10-01 09:00:00",
  "end_at": "2024-10-01 17:00:00",
  "capacity": 200,
  "register_deadline": "2024-09-25 23:59:59",
  "speakers": [{"name": "張三", "topic": "AI 趨勢"}]
}

-- 下載
{
  "file_url": "/files/driver_v2.1.zip",
  "file_size": 10245678,
  "version": "2.1.0",
  "platforms": ["Windows", "macOS", "Linux"],
  "checksum": "sha256:abc123..."
}

查詢範例 (MySQL 5.7+ JSON 函式)

-- 查詢價格 > 1000 的產品
SELECT id, title, extra->>'$.price' AS price
FROM contents
WHERE type = 'product' 
  AND JSON_EXTRACT(extra, '$.price') > 1000
  AND status = 1;

-- 查詢下月舉行的活動
SELECT id, title, extra->>'$.venue' AS venue, extra->>'$.start_at' AS start_at
FROM contents
WHERE type = 'event'
  AND JSON_EXTRACT(extra, '$.start_at') BETWEEN '2024-11-01' AND '2024-11-30';

-- 建立虛擬欄位索引 (MySQL 8.0)
ALTER TABLE contents 
ADD COLUMN price_virtual DECIMAL(10,2) 
GENERATED ALWAYS AS (JSON_EXTRACT(extra, '$.price')) VIRTUAL,
ADD INDEX idx_price (price_virtual);

應用層封裝 (PHP 範例)

<?php
class ContentRepository {
    private $pdo;
    
    public function __construct(PDO $pdo) { $this->pdo = $pdo; }
    
    public function create(array $data): int {
        $sql = "INSERT INTO contents (type, title, body, status, published_at, extra) 
                VALUES (?, ?, ?, ?, ?, ?)";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([
            $data['type'],
            $data['title'],
            $data['body'],
            $data['status'] ?? 1,
            $data['published_at'] ?? null,
            json_encode($data['extra'] ?? [], JSON_UNESCAPED_UNICODE)
        ]);
        return (int)$this->pdo->lastInsertId();
    }
    
    public function findByTypeAndFilter(string $type, array $filters = []): array {
        $sql = "SELECT * FROM contents WHERE type = ? AND status = 1";
        $params = [$type];
        
        foreach ($filters as $path => $value) {
            $sql .= " AND JSON_EXTRACT(extra, ?) = ?";
            $params[] = $path; // 如 '$.price'
            $params[] = $value;
        }
        
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}
?>

遷移策略:從舊結構平滑過渡

  1. 新增 extra JSON 欄位至現有表
  2. 寫入遷移腳本:將舊延伸表/EAV 資料轉為 JSON 寫入 extra
  3. 雙寫期:應用層同時寫入舊結構與 extra
  4. 驗證無誤後,移除舊表/欄位,下線舊代碼

總結

資訊發布系統的彈性資料模型,核心表 + JSON 擴充欄位 是現代關聯式資料庫 (MySQL 5.7+, PostgreSQL) 的最佳平衡點:保留結構化查詢優勢,同時獲得 Schema-less 彈性。避免單大表的 NULL 洪水、EAV 的效能地獄、多表繼承的 Join 地獄。若專案已採用微服務/文件資料庫,直接使用 MongoDB 更自然。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved