Skip to content

第 9 章 触发器、权限管理、事务与并发控制

本章知识图谱

图表渲染中…

9.1 触发器概述

触发器 Trigger 本质上是一种特殊的存储过程,它在指定表发生插入、更新或删除事件时自动触发执行。

触发器符合 ECA 规则:

text
Event 事件 -> Condition 条件 -> Action 动作

在 MySQL 中,触发器事件包括:

  • INSERT
  • UPDATE
  • DELETE

触发时间包括:

  • BEFORE
  • AFTER

触发器作用:

  • 强化约束,表达主键、外键、检查约束难以表达的复杂规则。
  • 跟踪变化,记录审计日志。
  • 级联运行,在一张表变化时维护另一张表。
  • 调用或封装业务逻辑。

9.2 触发器创建与使用

基本语法:

sql
CREATE TRIGGER trigger_name
trigger_time trigger_event
ON tbl_name
FOR EACH ROW
trigger_stmt;

参数:

参数说明
trigger_name触发器名称
trigger_timeBEFOREAFTER
trigger_eventINSERTUPDATEDELETE
tbl_name触发器绑定的表
FOR EACH ROWMySQL 触发器按行触发
trigger_stmt被触发执行的语句或语句块

示例:统计插入次数。

sql
CREATE TABLE test (a INT);
SET @count = 0;

DELIMITER $$

CREATE TRIGGER insert_test
AFTER INSERT ON test
FOR EACH ROW
BEGIN
  SET @count = @count + 1;
END $$

DELIMITER ;

INSERT INTO test VALUES (11);
SELECT @count;

OLD 与 NEW

MySQL 触发器中可使用 OLDNEW 访问变化前后的行。

事件可用对象说明
INSERTNEW新插入的行
DELETEOLD被删除的旧行
UPDATEOLDNEW更新前和更新后的行

使用方式:

sql
NEW.column_name
OLD.column_name

OLD 是只读的;在 BEFORE 触发器中,某些情况下可修改 NEW 的值。

示例:插入学生后维护班级人数。

sql
DELIMITER $$

CREATE TRIGGER tri_stu_insert
AFTER INSERT ON student
FOR EACH ROW
BEGIN
  UPDATE class
  SET stuCount = stuCount + 1
  WHERE classID = NEW.classID;
END $$

DELIMITER ;

查看和删除触发器

sql
SHOW TRIGGERS;
SHOW TRIGGERS FROM db_name;

SELECT *
FROM information_schema.TRIGGERS
WHERE TRIGGER_SCHEMA = 'db_name';

DROP TRIGGER IF EXISTS tri_stu_insert;

触发器限制

  • MySQL 触发器按行触发,批量操作可能触发很多次,性能需关注。
  • 触发器不能使用显式或隐式开始/结束事务的语句,如 START TRANSACTIONCOMMITROLLBACK
  • 触发器不适合写复杂、不可控、隐蔽的业务逻辑,否则调试困难。
  • MyISAM 不支持事务,触发器失败时不能保证相关操作原子回滚。

9.3 事件

事件调度器 Event Scheduler 可按时间触发任务,也可理解为“时间触发器”。它适合定期清理、汇总、归档等任务。

触发器与事件区别:

对比触发器事件
触发条件表上的 INSERTUPDATEDELETE指定时间或周期
绑定对象数据库事件调度器
典型用途审计、级联维护、复杂约束定期清理、定时报表、汇总

开启事件调度器:

sql
SET GLOBAL event_scheduler = ON;
SELECT @@global.event_scheduler;

创建事件:

sql
CREATE EVENT [IF NOT EXISTS] event_name
ON SCHEDULE schedule
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE | DISABLE ON SLAVE]
[COMMENT 'comment']
DO event_body;

周期执行:

sql
CREATE EVENT IF NOT EXISTS e_t
ON SCHEDULE EVERY 5 SECOND
ON COMPLETION PRESERVE
DO
  INSERT INTO tb_etest(user, createtime)
  VALUES ('MySQL', NOW());

指定时间执行:

sql
CREATE EVENT demo_event1
ON SCHEDULE AT TIMESTAMP '2026-06-23 15:29:47'
DO
  INSERT INTO demo(name, createtime)
  VALUES ('hello', NOW());

延迟执行:

sql
CREATE EVENT demo_event2
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 5 HOUR
DO
  INSERT INTO demo(name, createtime)
  VALUES ('hello', NOW());

修改事件:

sql
ALTER EVENT event_name DISABLE;
ALTER EVENT event_name ENABLE;
ALTER EVENT event_name RENAME TO new_event_name;

删除事件:

sql
DROP EVENT IF EXISTS event_name;

9.4 权限管理

权限管理用于控制哪些用户可以访问哪些数据库对象,以及可以执行哪些操作。

访问控制相关表

MySQL 系统库 mysql 中维护权限相关表:

作用
user用户级权限和账户信息
db数据库级权限
host主机相关权限,旧版本中常见
tables_priv表级权限
columns_priv列级权限
procs_priv存储过程和函数权限

常见权限字段:

  • select_priv
  • insert_priv
  • update_priv
  • delete_priv
  • create_priv
  • drop_priv
  • grant_priv
  • references_priv
  • index_priv

用户管理

创建用户:

sql
CREATE USER 'lily'@'localhost' IDENTIFIED BY '123456';

查看用户:

sql
SELECT host, user
FROM mysql.user;

修改用户名:

sql
RENAME USER 'lily'@'localhost' TO 'lucy'@'localhost';

修改密码:

sql
ALTER USER 'lucy'@'localhost' IDENTIFIED BY 'new_password';

旧式或命令行方式:

bash
mysqladmin -u username -p password

删除用户:

sql
DROP USER 'lucy'@'localhost';

注意:'user'@'localhost''user'@'%' 是不同账户。

授权 GRANT

基本语法:

sql
GRANT priv_type [(column_list)] [, priv_type [(column_list)] ...]
ON [object_type] priv_level
TO 'user'@'host'
[WITH GRANT OPTION];

权限级别:

写法含义
*.*所有数据库所有表
db_name.*某数据库所有表
db_name.table_name某数据库某表
table_name当前数据库中的某表

示例:

sql
GRANT SELECT, INSERT
ON jxgl.student
TO 'lily'@'localhost';

GRANT ALL PRIVILEGES
ON jxgl.*
TO 'dba_user'@'localhost'
WITH GRANT OPTION;

WITH GRANT OPTION 表示被授权者可以把自己拥有的权限再授予别人,应谨慎使用。

撤销权限 REVOKE

sql
REVOKE INSERT
ON jxgl.student
FROM 'lily'@'localhost';

REVOKE ALL PRIVILEGES, GRANT OPTION
FROM 'lily'@'localhost';

旧版本直接修改权限表后需要:

sql
FLUSH PRIVILEGES;

使用 CREATE USERGRANTREVOKE 通常会自动处理权限缓存。

资源限制

授权时可限制资源:

sql
GRANT SELECT
ON jxgl.*
TO 'lily'@'localhost'
WITH MAX_QUERIES_PER_HOUR 100
     MAX_UPDATES_PER_HOUR 20
     MAX_CONNECTIONS_PER_HOUR 10
     MAX_USER_CONNECTIONS 2;

9.5 事务处理

事务是用户定义的一个数据库操作序列,这些操作要么全部执行,要么全不执行。事务是不可分割的逻辑工作单元。

ACID

特性含义
Atomicity 原子性事务中的操作要么全做,要么全不做
Consistency 一致性事务执行前后数据库从一个一致状态到另一个一致状态
Isolation 隔离性并发事务之间互相隔离,未完成结果不应随意暴露
Durability 持久性事务提交后,其影响应永久保存

事务控制语句

sql
START TRANSACTION;
-- 或
BEGIN;

COMMIT;
ROLLBACK;

SET autocommit = 0;
SET autocommit = 1;

MySQL 默认通常是自动提交。若需要显式提交和回滚,可:

sql
SET autocommit = 0;

UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

COMMIT;

回滚示例:

sql
START TRANSACTION;
DELETE FROM ordertotals;
ROLLBACK;

提交示例:

sql
START TRANSACTION;
DELETE FROM t_orderitems WHERE order_num = 2021;
DELETE FROM t_orders WHERE order_num = 2021;
COMMIT;

注意:某些 DDL 语句如 CREATEDROP 可能隐式提交,不能简单依赖 ROLLBACK 撤销。

调度与正确性

调度是一个或多个事务的重要操作序列。

  • 串行调度:不同事务的操作不交叉。
  • 非串行调度:不同事务的操作交叉执行。

并发执行可以提高吞吐量,但可能导致一致性问题。目标是让并发调度在效果上等价于某个串行调度,即可串行化。

并发异常

异常含义
丢失更新两个事务更新同一数据,一个更新覆盖另一个更新
脏写一个事务覆盖了另一个未提交事务写入的数据
脏读一个事务读取到另一个未提交事务的数据
不可重复读同一事务两次读取同一行,结果不同
幻读同一事务两次读取同一范围,出现新增或消失的行

隔离级别

MySQL 提供四种标准隔离级别:

sql
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
  READ UNCOMMITTED;

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
  READ COMMITTED;

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
  REPEATABLE READ;

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
  SERIALIZABLE;

查看隔离级别:

sql
SELECT @@transaction_isolation;
-- 旧版本也可能使用
SELECT @@tx_isolation;
隔离级别脏读不可重复读幻读说明
READ UNCOMMITTED可能可能可能隔离性最低
READ COMMITTED避免可能可能只能读已提交数据
REPEATABLE READ避免避免可能或由 InnoDB 机制处理MySQL InnoDB 默认
SERIALIZABLE避免避免避免最强隔离,并发最低

InnoDB 默认 REPEATABLE READ,结合 MVCC 和锁机制处理很多并发问题。

9.6 并发控制与锁

并发控制用于在多事务同时访问数据库时保证正确性。

锁粒度

锁粒度优点缺点
表级锁开销小,加锁快,不易死锁粒度大,并发低
行级锁粒度小,并发高开销大,可能死锁

MyISAM 主要使用表级锁。InnoDB 支持事务和行级锁。

MyISAM 表级锁

MyISAM 在执行查询前自动加读锁,在执行 UPDATEDELETEINSERT 前自动加写锁。

表锁模式:

  • 表共享读锁 table read lock
  • 表独占写锁 table write lock

InnoDB 行级锁

InnoDB 行锁通过给索引项加锁实现。若查询没有使用索引,可能退化为锁住大量记录。

行锁模式:

含义语法
共享锁 S允许读,阻止其他事务获取排他锁LOCK IN SHARE MODE
排他锁 X允许更新,阻止其他事务获取共享锁和排他锁FOR UPDATE

示例:

sql
SELECT *
FROM student
WHERE sno = '20240001'
LOCK IN SHARE MODE;

SELECT *
FROM student
WHERE sno = '20240001'
FOR UPDATE;

死锁

死锁是多个事务互相等待对方持有的资源,导致都无法继续执行。

典型场景:

text
T1 锁住 R1,等待 R2
T2 锁住 R2,等待 R1
T1 等 T2,T2 等 T1,形成死锁

避免死锁建议:

  • 不同程序按相同顺序访问表和记录。
  • 批量处理前先排序,使线程按固定顺序处理记录。
  • 事务尽量短,减少持锁时间。
  • 需要更新时尽早申请足够级别的锁。
  • 减少交互式长事务。
  • 为高频更新条件建立合适索引,避免锁范围扩大。

本章易错点

  • 触发器是自动执行的,存储过程通常需要显式调用。
  • MySQL 触发器是 FOR EACH ROW,批量操作会触发多次。
  • 触发器里不要写 COMMITROLLBACK
  • 'user'@'localhost''user'@'%' 是不同用户。
  • WITH GRANT OPTION 是权限再授权能力,不是普通权限。
  • MySQL 默认自动提交,事务前要明确 START TRANSACTION 或关闭 autocommit
  • READ COMMITTED 不能保证可重复读。
  • 行锁依赖索引,没用好索引可能锁范围很大。

自测题

  1. 触发器的 ECA 分别指什么?
  2. OLDNEW 在三类触发事件中如何使用?
  3. 事件调度器和触发器的触发条件有什么区别?
  4. GRANT ... WITH GRANT OPTION 的含义是什么?
  5. ACID 四个特性分别解决什么问题?
  6. 脏读、不可重复读、幻读如何区分?
  7. 为什么 InnoDB 行锁依赖索引?
  8. 死锁产生的必要直观条件是什么?如何降低概率?