在 MySQL 客戶端 (mysql 命令行、Workbench、腳本檔) 執行多語句腳本時,`DELIMITER` 是避免語句提前終止的關鍵。本文徹底講清其原理、使用場景、常見誤區與替代方案。
MySQL 客戶端預設以 **分號 (`;`)** 判斷語句結束。當腳本包含 **複合語句** (存儲過程、函數、觸發器、事件、複合 `BEGIN ... END` 區塊) 時,內部也有分號,客戶端會誤以為語句結束,導致語法錯誤或語句被截斷執行。
-- 1. 修改分隔符為自定義字串 (常用 // 或 $$)
DELIMITER //
-- 2. 編寫包含分號的複合語句
CREATE PROCEDURE GetUserCount()
BEGIN
DECLARE cnt INT DEFAULT 0;
SELECT COUNT(*) INTO cnt FROM users;
SELECT cnt;
END //
-- 3. 恢復預設分隔符
DELIMITER ;
客戶端讀取輸入流
│
├─ 未遇到 DELIMITER 指定的分隔符 → 繼續緩衝
│
└─ 遇到分隔符 → 將緩衝區內容作為**一條完整語句**發送給 Server 執行
│
├─ Server 解析、優化、執行
│
└─ 返回結果給客戶端
| 分隔符 | 優點 | 缺點 | 建議場景 |
|---|---|---|---|
| `//` | 簡短、鍵盤易輸入、不衝突 | 極少數 SQL 內容可能包含 | 通用首選 |
| `$$` | PostgreSQL 風格、視覺醒目 | 需輸入兩字符 | 習慣 PG 的團隊 |
| `;;` | 雙分號、直觀 | 易與單分號混淆 | 不推薦 |
| `|` / `#` | 單字符 | 極易與 SQL 運算符/註釋衝突 | **嚴禁** |
DELIMITER $$
-- 1. 函數
CREATE FUNCTION GetFullName(p_first VARCHAR(50), p_last VARCHAR(50))
RETURNS VARCHAR(101) DETERMINISTIC
BEGIN
RETURN CONCAT(p_first, ' ', p_last);
END $$
-- 2. 存儲過程
CREATE PROCEDURE UpdateUserEmail(IN p_user_id BIGINT, IN p_new_email VARCHAR(100))
BEGIN
DECLARE old_email VARCHAR(100);
SELECT email INTO old_email FROM users WHERE id = p_user_id;
IF old_email IS NOT NULL THEN
UPDATE users SET email = p_new_email WHERE id = p_user_id;
INSERT INTO email_log (user_id, old_email, new_email, changed_at)
VALUES (p_user_id, old_email, p_new_email, NOW(6));
END IF;
END $$
-- 3. 觸發器
CREATE TRIGGER trg_user_before_update
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
IF NEW.email != OLD.email THEN
SET NEW.updated_at = NOW(6);
END IF;
END $$
DELIMITER ;
| 誤區 | 後果 | 正確做法 |
|---|---|---|
| 在應用程式代碼 (Java/Python/PHP) 中使用 `DELIMITER` | 驅動程式不識別,報語法錯誤 | **僅在 MySQL 客戶端/腳本檔** 使用;應用層用單條 SQL 或多語句支援 (如 `allowMultiQueries=true`) |
| 忘記恢復 `DELIMITER ;` | 後續普通語句無法執行,客戶端一直等待分隔符 | 腳本結尾**必須**恢復 |
| 巢狀 `BEGIN ... END` 使用同一分隔符 | 內層 `END //` 會提前結束外層定義 | MySQL 不支援巢狀複合語句定義,拆分為多個獨立 `CREATE` |
| 分隔符與 SQL 內容衝突 | 語句被錯誤分割 | 選用極低衝突概率的字串 (如 `//`、`$$`),避免單字符 |
| 以為 `DELIMITER` 是 SQL 標準語句 | 移植到其他資料庫失效 | **僅為 MySQL 客戶端指令**,Server 不識別,標準 SQL 用 `BEGIN ATOMIC ... END` (少數支援) |
CREATE OR REPLACE FUNCTION AddOne(n INT) RETURNS INT RETURN n + 1;
搞懂 `DELIMITER` 不是為了炫技,而是為了在遷移腳本、初始化腳本、存儲過程部署時,**不再被「語句未結束」或「語法錯誤」卡住**,專注於業務邏輯本身。