- Published on
停止在資料庫中使用布林值?探討日期時間欄位的替代方案
AI 產生的圖片,粗暴直覺又有力,我喜歡 XD
最近在 YouTube 上看到一個有趣的技術討論:「你應該停止在資料庫中使用布林值」。這個看似簡單卻引發廣泛爭議的觀點,讓我重新思考了資料庫設計中的型別選擇問題。
影片觀點:日期時間欄位的優勢
傳統布林值方式
在傳統的資料庫設計中,我們習慣使用布林值來表示二元狀態:
CREATE TABLE todos (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title VARCHAR(255) NOT NULL,
is_completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW()
);
這種設計簡單直觀,但只能告訴我們「是否完成」,無法提供更多上下文資訊。
日期時間欄位的替代方案
影片提出的替代方案是使用日期時間欄位:
CREATE TABLE todos (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title VARCHAR(255) NOT NULL,
completed_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT NOW()
);
這種設計的優勢在於:
- 豐富的上下文資訊:當
completed_at
有值時,表示已完成,且能知道確切的完成時間 - 節省欄位空間:一個欄位同時表達「是否完成」和「何時完成」
- 更好的數據洞察:可以分析完成時間的分佈、趨勢等
-- 查詢已完成的任務
SELECT * FROM todos WHERE completed_at IS NOT NULL;
-- 查詢未完成的任務
SELECT * FROM todos WHERE completed_at IS NULL;
-- 分析完成時間分佈
SELECT
DATE(completed_at) as completion_date,
COUNT(*) as completed_tasks
FROM todos
WHERE completed_at IS NOT NULL
GROUP BY DATE(completed_at);
我的觀點:不是萬靈丹
雖然這個想法很有趣,但我認為這兩種資料型別本質上服務於不同的目的。日期時間欄位雖然可以運作,但我不會全盤採用這種做法。
布林值的適用場景
某些情況下,布林值仍然是最佳選擇:
CREATE TABLE users (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
is_email_verified BOOLEAN DEFAULT FALSE,
is_admin BOOLEAN DEFAULT FALSE,
email_verified_at TIMESTAMP NULL
);
在這個例子中:
is_admin
是一個純粹的權限狀態,不需要時間資訊email_verified_at
提供驗證時間,is_email_verified
提供快速查詢
Enum:更好的替代選擇
如果真的要取代布林值,我更傾向於使用 Enum 型別:
CREATE TYPE task_status AS ENUM ('pending', 'in_progress', 'completed', 'cancelled');
CREATE TABLE tasks (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title VARCHAR(255) NOT NULL,
status task_status DEFAULT 'pending',
completed_at TIMESTAMP NULL,
cancelled_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT NOW()
);
這種設計的優勢:
- 語意明確:狀態值具有明確的業務含義
- 擴展性佳:可以輕鬆添加新狀態
- 型別安全:資料庫層面的約束確保資料完整性
- 查詢效率:Enum 型別通常比字串比較更高效
複雜場景的考量
狀態變更追蹤
當需要追蹤狀態變更歷史時,單純的日期時間欄位會遇到限制:
-- 如果任務從完成變回進行中,completed_at 該如何處理?
UPDATE tasks SET completed_at = NULL WHERE id = 1;
這種情況下,影片提到的事件溯源(Event Sourcing)模式更適合:
CREATE TABLE task_events (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
task_id INTEGER REFERENCES tasks(id),
event_type VARCHAR(50) NOT NULL, -- 'created', 'started', 'completed', 'reopened'
occurred_at TIMESTAMP DEFAULT NOW(),
metadata JSONB
);
混合方案
在實務中,我經常採用混合方案:
CREATE TABLE orders (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
status order_status DEFAULT 'pending',
is_paid BOOLEAN DEFAULT FALSE,
paid_at TIMESTAMP NULL,
shipped_at TIMESTAMP NULL,
delivered_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
這種設計結合了各種方案的優點:
status
Enum 提供清晰的狀態管理is_paid
布林值提供快速查詢- 各種時間戳記提供詳細的歷史資訊
效能與索引考量
不同的資料型別對查詢效能也有影響:
-- 布林值索引
CREATE INDEX idx_todos_completed ON todos (is_completed);
-- 日期時間欄位的部分索引(PostgreSQL)
CREATE INDEX idx_todos_completed_at ON todos (completed_at)
WHERE completed_at IS NOT NULL;
-- Enum 索引
CREATE INDEX idx_tasks_status ON tasks (status);
布林值索引通常具有較低的選擇性,但在某些場景下仍然有效。日期時間欄位可以利用部分索引來優化 NULL 值的處理。
實務建議
根據我的經驗,選擇合適的資料型別應該考慮以下因素:
1. 業務需求分析
- 是否需要追蹤時間資訊?
- 狀態是否會多次變更?
- 是否需要狀態變更的歷史記錄?
2. 查詢模式
- 最常見的查詢是什麼?
- 是否需要複雜的狀態組合查詢?
- 效能要求如何?
3. 未來擴展性
- 狀態是否可能增加?
- 業務邏輯是否會變得更複雜?
4. 團隊慣例
- 團隊對不同方案的熟悉程度
- 現有系統的一致性考量
結論
「停止使用布林值」這個觀點雖然新穎,但確實提醒我們重新思考資料庫設計的選擇。我的建議是:
- 不要一刀切:每種資料型別都有其適用場景
- 理解業務需求:選擇最符合業務邏輯的方案
- 考慮混合使用:不同型別可以互補,發揮各自優勢
- 重視可維護性:選擇團隊能夠理解和維護的方案
正如影片最後所說:「這取決於具體情況。」最重要的是理解每一個技術選擇背後的「為什麼」,而不是盲目地追隨某種趨勢。
在資料庫設計中,沒有銀彈,只有在特定情境下的最佳解答。作為開發者,我們需要在簡潔性、效能、可維護性和業務需求之間找到平衡點。
支持創作
如果這篇文章對您有幫助,歡迎透過 贊助連結 支持我持續創作優質內容。您的支持是我前進的動力!
圖片來源:AI 產生