利用整數的二進位位元,將多個獨立的布林/枚舉狀態壓縮儲存於單一 INT UNSIGNED 欄位。透過位元 AND(&)查詢、位元 OR(|)設定、位元 XOR(^)切換,實現極高效能的多屬性索引與過濾。
| 運算 | 符號 | 真值表 | 助記 |
|---|---|---|---|
| AND | & | 1&1=1, 其餘=0 | 「全 1 才 1」,如同乘法 |
| OR | | | 0|0=0, 其餘=1 | 「有 1 即 1」,如同加法 |
| NOT | ~ | 0→1, 1→0 | 翻轉位元 |
users.status INT UNSIGNED NOT NULL DEFAULT 0位元分配(從低位到高位):
| 位元區間 | 用途 | 值定義 | 遮罩 |
|---|---|---|---|
| 0-2 (3 bits) | 註冊狀態 | 0=未審, 1=通過, 2=高級, 3=管理, 4=超管 | 0b111 = 7 |
| 3-5 (3 bits) | 性別 | 0=未知, 1=男, 2=女 | 0b111000 = 56 |
| 6-8 | 會員等級 | 0=普通, 1=銀, 2=金, 3=鑽 | 0b111000000 = 448 |
| 9-31 | 保留擴充 | — | — |
<?php
// 狀態常數
define('USER_NEW', 0b000); // 0
define('USER_NORMAL', 0b001); // 1
define('USER_ADVANCE', 0b010); // 2
define('USER_MANAGE', 0b011); // 3
define('USER_SUPER', 0b100); // 4
define('USER_MASK', 0b111); // 7
define('GENDER_UNKNOWN', 0b000000); // 0
define('GENDER_MALE', 0b001000); // 8 (1 << 3)
define('GENDER_FEMALE', 0b010000); // 16 (2 << 3)
define('GENDER_MASK', 0b111000); // 56
// 查詢建構函式
function buildWhere(int $status, int $mask): string {
return "(`status` & $mask) = $status";
}
// 使用範例
$sql = "SELECT * FROM users WHERE " . buildWhere(GENDER_MALE, GENDER_MASK);
// → SELECT * FROM users WHERE (status & 56) = 8
$sql = "SELECT * FROM users WHERE " . buildWhere(USER_MANAGE, USER_MASK);
// → SELECT * FROM users WHERE (status & 7) = 3
// 組合條件:男性且為管理員
$combinedStatus = GENDER_MALE | USER_MANAGE; // 8 | 3 = 11
$combinedMask = GENDER_MASK | USER_MASK; // 56 | 7 = 63
$sql = "SELECT * FROM users WHERE " . buildWhere($combinedStatus, $combinedMask);
// → (status & 63) = 11
// 排除條件:非「未審核」
$sql = "SELECT * FROM users WHERE (status & " . USER_MASK . ") != " . USER_NEW;
?>
<?php
trait BitwiseFlags {
protected int $flags = 0;
public function hasFlag(int $mask, int $value = null): bool {
if ($value === null) return ($this->flags & $mask) !== 0;
return ($this->flags & $mask) === $value;
}
public function setFlag(int $mask, int $value): self {
$this->flags = ($this->flags & ~$mask) | ($value & $mask);
return $this;
}
public function toggleFlag(int $mask): self {
$this->flags ^= $mask;
return $this;
}
public function getRawFlags(): int {
return $this->flags;
}
}
// 使用
class User {
use BitwiseFlags;
// ...
}
$user = new User();
$user->setFlag(GENDER_MASK, GENDER_MALE)
->setFlag(USER_MASK, USER_MANAGE);
echo $user->hasFlag(GENDER_MASK, GENDER_MALE); // true
?>
| 優點 | 缺點 |
|---|---|
| 單欄位儲存多屬性,節省結構修改成本 | 語義隱含於程式碼常數,DB 直接看不懂 |
位元運算極快,索引友善(status & mask = val 可用索引) | 單一屬性值域受限於位元寬度(3 bits 只能 0-7) |
| 組合查詢單一 WHERE 條件,減少 JOIN | 需嚴格管理常數定義,避免位元衝突 |
| 新增屬性只需分配新位元區,無 ALTER TABLE | 不適用於高基數屬性(如標籤、分類) |
位元遮罩是關聯式資料庫中實現「欄位複用」的經典技巧,配合 PHP 位元運算子與常數定義,能以極低儲存成本支援高效能的多維度查詢。關鍵在於嚴謹的位元規劃、集中管理常數、封裝操作介面,避免硬編碼散落導致維護災難。