触发器应建在products表的UPDATE事件上,用于捕获price字段变更;用OLD.price和NEW.price判异后写入日志;日志表需避免外键、冗余字段以及时区问题。

触发器该建在哪个表、哪个事件上
价格变更日志的核心是捕获 products 表中 price 字段的修改,所以触发器必须建在 products 表上,且只响应 UPDATE 事件。别在 INSERT 或 DELETE 上建——新增商品不等于调价,删商品也不产生价格变更记录。
常见错误是把触发器建在订单或库存表上,结果根本抓不到源头价格变动。另外,如果业务允许批量更新(比如运营后台导表改价),UPDATE 触发器天然支持逐行触发,无需额外循环逻辑。
如何安全读取旧值和新值
MySQL 触发器里用 OLD.price 拿更新前的价格,NEW.price 拿更新后的价格。关键点在于:必须先判断二者是否真的不同,否则每次 UPDATE(哪怕没改 price)都会写一条无效日志。
IF OLD.price != NEW.price THEN INSERT INTO price_change_log (product_id, old_price, new_price, updated_at) VALUES (OLD.id, OLD.price, NEW.price, NOW()); END IF;
- 注意用
!=而不是<>,语义更清晰;两者等价,但团队习惯优先 - 如果
price允许为NULL,得改成OLD.price <> NEW.price OR (OLD.price IS NULL) <> (NEW.price IS NULL),否则NULL != 1返回UNKNOWN,条件不成立 - 别在触发器里查其他表(比如商品名称),会拖慢主更新,也容易死锁
日志表结构要避开哪些坑
price_change_log 表不能直接外键关联 products,否则 products 行被删时,触发器执行可能失败(尤其级联删除场景)。应该用普通字段存 product_id,靠应用层保证一致性。
字段设计要点:
-
product_id类型必须和products.id完全一致(比如都是BIGINT UNSIGNED),否则隐式转换导致索引失效 - 加联合索引
(product_id, updated_at),方便查某商品所有调价历史 - 别用
TIMESTAMP存updated_at,它受时区影响;统一用DATETIME+ 应用层传 UTC 时间 - 避免在日志表里存冗余字段如
product_name,名字变一次就得连带更新所有历史日志,违背日志不可变原则
触发器上线前必须验证的边界情况
真实电商场景下,这些操作会绕过触发器或引发异常:
- 用
UPDATE products SET price = price * 0.9 WHERE category = 'clearance'批量调价 —— 触发器正常工作,但要注意OLD.price和NEW.price是计算后值,没问题 - 直接执行
UPDATE products SET price = 99.9 WHERE id = 123,但该行price原值就是99.9—— 触发器不执行,前提是判断逻辑写对了 - 用
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE更新商品 —— 这些语句会触发UPDATE触发器,但要注意它们可能先删后插,OLD值仍可取到 - 事务中更新商品又回滚 —— 触发器写入的日志也会自动回滚,不用额外处理
最易忽略的是字符集和排序规则不一致导致的比较异常,比如 price 是 DECIMAL(10,2) 但用了 utf8mb4_0900_as_cs 排序规则——虽然少见,但在跨库迁移后可能出问题,上线前务必在测试库跑一遍价格变更全流程。


















