MySQL 觸發器在 BEFORE INSERT/UPDATE 階段操作觸發表自身的資料時,嚴禁使用 UPDATE 語句,否則拋出錯誤:
ERROR 1442 (HY000): Can't update table 'dept' in stored function/trigger
because it is already used by statement which invoked this stored function/trigger.
原因:觸發器執行時,表已被觸發語句鎖定/使用,無法在同一事務中再次對該表執行 DML。
DELIMITER |
CREATE TRIGGER replaceGangcn
BEFORE INSERT ON dept
FOR EACH ROW
BEGIN
-- 錯誤:嘗試 UPDATE 觸發表自身
UPDATE dept SET name = REPLACE(NEW.name, '-', ' ');
END |
DELIMITER ;
DELIMITER |
CREATE TRIGGER replaceGangcn
BEFORE INSERT ON dept
FOR EACH ROW
BEGIN
-- 正確:直接修改 NEW 伪列,資料寫入前生效
SET NEW.name = REPLACE(NEW.name, '-', ' ');
END |
DELIMITER ;
| 場景 | 正確做法 | 錯誤做法 |
|---|---|---|
| BEFORE INSERT/UPDATE 修改本表當前行 | SET NEW.col = ... |
UPDATE table SET col = ... |
| BEFORE INSERT/UPDATE 讀取本表其他行 | SELECT ... FROM table WHERE ...(允許讀) |
無 |
| AFTER INSERT/UPDATE 修改本表其他行 | UPDATE table SET ... WHERE ...(允許寫其他行) |
無 |
| BEFORE DELETE 讀取被刪行 | SELECT ... FROM table WHERE ... 用 OLD.col |
無 |
| AFTER DELETE 修改本表其他行 | UPDATE table SET ... WHERE ... |
無 |
| 觸發時機 | NEW | OLD |
|---|---|---|
| BEFORE INSERT | 可讀寫(新行值) | 不可用 |
| AFTER INSERT | 可讀(已寫入值) | 不可用 |
| BEFORE UPDATE | 可讀寫(新值) | 可讀(舊值) |
| AFTER UPDATE | 可讀(已更新值) | 可讀(舊值) |
| BEFORE DELETE | 不可用 | 可讀(被刪值) |
| AFTER DELETE | 不可用 | 可讀(被刪值) |
-- 1. 插入前自動生成代碼:前綴 + 序號
CREATE TRIGGER gen_code_before_insert
BEFORE INSERT ON products
FOR EACH ROW
BEGIN
DECLARE next_seq INT;
SELECT COALESCE(MAX(CAST(SUBSTRING(code, 4) AS UNSIGNED)), 0) + 1
INTO next_seq FROM products WHERE code LIKE 'PRD-%';
SET NEW.code = CONCAT('PRD-', LPAD(next_seq, 6, '0'));
END;
-- 2. 更新前記錄變更日誌(寫入另一張表)
CREATE TRIGGER log_price_change
BEFORE UPDATE ON products
FOR EACH ROW
BEGIN
IF NEW.price <> OLD.price THEN
INSERT INTO price_log (product_id, old_price, new_price, changed_at)
VALUES (OLD.id, OLD.price, NEW.price, NOW(6));
END IF;
END;
-- 3. 刪除前歸檔資料(寫入歸檔表)
CREATE TRIGGER archive_before_delete
BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
INSERT INTO orders_archive SELECT * FROM orders WHERE id = OLD.id;
END;
-- 4. 複雜驗證:插入前檢查業務規則
CREATE TRIGGER validate_stock_before_insert
BEFORE INSERT ON order_items
FOR EACH ROW
BEGIN
DECLARE available INT;
SELECT stock INTO available FROM products WHERE id = NEW.product_id;
IF available < NEW.quantity THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '庫存不足';
END IF;
END;
IF @trig_flag IS NULL THEN SET @trig_flag=1; ... END IF;INSERT INTO debug_log(msg) VALUES(CONCAT('TRIGGER: ', NEW.id)); 追蹤執行SET NEW.col = ...UPDATE/DELETE/INSERT 針對觸發表自身(BEFORE 階段)SIGNAL 錯誤代碼,應用層可捕獲處理MySQL 觸發器操作本表資料的鐵律:BEFORE 階段只能 SET NEW.xxx,不能 UPDATE 觸發表。掌握這一核心規則,配合 NEW/OLD 伪列語意、遞歸防護、複寫模式影響,即能編寫健壯、高效、可維護的觸發器邏輯,將資料完整性約束下沉至資料庫層,減輕應用層負擔。