UPDATE语句直接在SET子句中使用表达式更新字段最简单高效,如UPDATE products SET price = price * 1.1, updated_at = NOW() WHERE id = 123;计算列仅适用于只读派生值,不可被SET赋值;触发器适用于跨表联动或复杂校验。

UPDATE语句里直接用表达式更新字段最简单
绝大多数场景根本不需要计算列或触发器——UPDATE本身支持在SET子句中写任意表达式。比如把price上调10%,同时更新updated_at:
UPDATE products
SET price = price * 1.1,
updated_at = NOW()
WHERE id = 123;这种写法原子性强、性能好、逻辑一目了然。常见错误是试图先SELECT再UPDATE,结果在并发下被覆盖;或者误以为必须用变量存中间值,其实SQL引擎会按行实时计算。
计算列(Generated Column)只适合只读衍生值
MySQL 5.7+ 和 PostgreSQL 支持GENERATED ALWAYS AS定义的计算列,但它本质是虚拟字段,不能出现在SET里被“更新”。它的作用是自动维护派生值,比如:
ALTER TABLE products ADD COLUMN total_amount DECIMAL(10,2) GENERATED ALWAYS AS (quantity * price) STORED;
注意:STORED表示物理存储(可索引),VIRTUAL则每次查时计算。关键限制:你不能执行UPDATE ... SET total_amount = ...,会报错Column 'total_amount' cannot be assigned a value。它只适用于“永远不该手动改”的场景,比如full_name由first_name和last_name拼接而成。
触发器适合跨表联动或复杂业务校验
当更新A表某字段时,需要同步修改B表、记录日志、或做条件判断(比如库存不能为负),才该用触发器。例如防止stock被设为负数:
CREATE TRIGGER check_stock_before_update
BEFORE UPDATE ON products
FOR EACH ROW
BEGIN
IF NEW.stock < 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Stock cannot be negative';
END IF;
END;容易踩的坑:
- 触发器里的NEW和OLD是行级上下文,不能直接引用其他表字段(除非显式JOIN或子查询)
- MySQL不支持在触发器里对同一张表做DML(会报Can't update table in stored function/trigger)
- PostgreSQL允许,但要注意递归触发风险
- 触发器逻辑难调试,线上出问题时排查成本远高于纯SQL语句
别为了“自动”牺牲可读性和可控性
很多人想用计算列或触发器,其实是担心忘记更新关联字段。但更可靠的做法是:把计算逻辑封装进应用层函数或存储过程,或者用视图暴露派生值。真正需要数据库层强制保证的,只有极少数强一致性要求的场景(如金融流水余额)。多数业务字段的“自动更新”,靠开发时写对UPDATE语句、靠测试覆盖、靠Code Review,比依赖触发器更轻量、更透明。

















