本节摘要:事务控制让一组语句"要么全做、要么全不做",权限控制决定谁能做哪一类操作。本节讲 COMMIT、ROLLBACK、SAVEPOINT 的组合用法与 autocommit 的默认陷阱,再讲 GRANT 最小授权模型的落地清单。位置:DML 之上的安全层,其 ACID 特性的内部实现将在第 6 章展开。
扣库存和写订单是两条 UPDATE。想象程序执行完第一条、还没来得及执行第二条时进程崩了:库存扣了,订单没了,客户付了钱却查不到单。事务就是防这个的——把两条语句绑成一个整体,要么两条都成功,要么数据库假装什么都没发生过。
先看默认行为的陷阱。MySQL 默认 autocommit 打开,每条语句自成事务、立即提交。这意味着你以为"多条语句一起失败会一起回滚",实际上每条早已落盘。正确姿势:
SET autocommit = 0; -- 或显式 START TRANSACTION START TRANSACTION; UPDATE inventory SET stock = stock - 2 WHERE sku_id = 8001 AND warehouse = 'SH01' AND stock >= 2; -- 结果:Query OK, 1 row affected INSERT INTO orders (customer_id, amount, status) VALUES (1, 199.00, 2); -- 结果:Query OK, 1 row affected COMMIT; -- 两条同时生效;任何一步失败则 ROLLBACK,两条同时消失
注意那条 UPDATE 的 WHERE 里带着 AND stock >= 2:超卖防护要靠条件竞争一步完成,事务本身不能替你判断业务规则——它只保证"全部生效或全部不生效",不保证"语义正确"。这个区别在评审会上值得反复强调。
大流程不需要从头回滚。下单流程若含"创建订单 → 扣库存 → 发优惠券",优惠券失败未必要废掉整单:
START TRANSACTION; INSERT INTO orders (customer_id, amount, status) VALUES (1, 199.00, 2); SAVEPOINT after_order; UPDATE inventory SET stock = stock - 1 WHERE sku_id = 8001 AND warehouse = 'SH01' AND stock >= 1; -- 结果:Query OK, 1 row affected INSERT INTO coupon_grant (customer_id, coupon_id) VALUES (1, 55); -- 结果:ERROR 1062 Duplicate entry '55'(今日名额已抢完) ROLLBACK TO SAVEPOINT after_order; UPDATE orders SET status = 1 WHERE order_id = LAST_INSERT_ID(); COMMIT; -- 订单照常成立,只是没领到券
解读:SAVEPOINT 在事务内部立标记,ROLLBACK TO 只撤销标记之后的语句。用它的前提是业务语义明确"哪步可弃、哪步必成"。反过来提醒:事务不是越大越好,长事务持有锁和 undo 资源的时间更长,第 6 章会给出"事务要短"的量化理由——这里先立规矩:事务里别放 RPC 调用、别放文件 IO、别等用户确认。
DCL 家族的核心是 GRANT 与 REVOKE。评审会上权限问题的标准答案是四个字:最小授权。生产环境的用户按角色分:
-- 应用账号:只给业务库的增删改查 CREATE USER 'mall_app'@'10.0.%' IDENTIFIED BY '复杂密码'; GRANT SELECT, INSERT, UPDATE, DELETE ON mall.* TO 'mall_app'@'10.0.%'; -- 只读账号:给报表与数据分析 CREATE USER 'mall_ro'@'10.0.%' IDENTIFIED BY '另一串复杂密码'; GRANT SELECT ON mall.* TO 'mall_ro'@'10.0.%'; -- DBA 运维账号:结构变更与全局管理,不进应用配置 GRANT ALL PRIVILEGES ON *.* TO 'mall_dba'@'10.0.%' WITH GRANT OPTION; -- 回收示例 REVOKE DELETE ON mall.* FROM 'mall_app'@'10.0.%'; SHOW GRANTS FOR 'mall_app'@'10.0.%';
三个细节值得点名。其一,'用户'@'来源' 双重要素:@ 后面限定来源网段,10.0.% 表示只允许内网地址连接,杜绝公网裸奔。其二,应用账号不给 DROP、ALTER 这类结构权限——即使 SQL 注入发生,攻击者能偷数据也难以毁掉表结构。其三,8.0 引入角色(ROLE)机制,先把权限打包成角色再授予用户,权限审计和批量调整都省力。
背景:客服系统要看订单但不能看金额。操作:
CREATE USER 'cs_view'@'10.0.%' IDENTIFIED BY '密码'; GRANT SELECT (order_id, customer_id, status, created_at) ON mall.orders TO 'cs_view'@'10.0.%';
结果:该账号能查 orders 的指定列,SELECT amount 直接报权限错误。解读:MySQL 支持列级授权,配合视图(VIEW)能把"能看什么"表达得更清晰——权限设计本身也是一种数据设计。
要点回顾:autocommit 是默认陷阱,多步写入显式开事务;SAVEPOINT 提供局部反悔;事务要短,外部调用不进事务;最小授权四件套——按角色建账号、限来源网段、不给结构权限、定期审计。多表关联的取数难题,下一节见。