本文将系统讲解 MySQL 中事务的四大特性、隔离级别与实现原理,深入拆解锁机制的种类与应用场景,并结合典型死锁案例进行分析,为你构建起应对复杂一致性问题的坚实基础。
事务(Transaction)是数据库的一个操作序列,这些操作要么全部执行成功,要么全部不执行,具有原子性。
特性 | 含义 |
|---|---|
Atomicity(原子性) | 事务中的操作要么全部完成,要么全部不做 |
Consistency(一致性) | 事务执行前后数据要保持一致 |
Isolation(隔离性) | 多个事务之间互不干扰,彼此隔离 |
Durability(持久性) | 一旦事务提交,变更永久保存 |
START TRANSACTION;
UPDATE products SET stock = stock - 1 WHERE id = 1001;
INSERT INTO orders(user_id, product_id) VALUES (2001, 1001);
COMMIT;若其中一步失败,则整个操作应回滚,确保库存与订单保持一致。
START TRANSACTION;
-- 或者 BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;某些语句自动开启事务,如 INSERT、UPDATE、DELETE,但只有在 autocommit=0 时生效。
ROLLBACK;用于中止事务,将所有变更撤销。
事务隔离是控制事务间读写操作的机制,关系到并发一致性问题。
隔离级别 | 说明 | 会产生的问题 |
|---|---|---|
READ UNCOMMITTED | 可以读到未提交数据 | 脏读(Dirty Read) |
READ COMMITTED | 只能读到已提交数据 | 不可重复读(Non-repeatable Read) |
REPEATABLE READ | 同一事务内多次读取结果一致 | 幻读(Phantom Read) |
SERIALIZABLE | 串行执行事务,最高隔离级别 | 性能最差 |
-- 查询当前隔离级别
SELECT @@tx_isolation;
-- 设置为可重复读(默认)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;读到了其他事务未提交的数据。
两次查询同一数据,结果不同。
事务期间查询记录总数发生变化(如新增或删除记录)。
InnoDB 引擎支持多种锁类型,确保并发事务安全。
-- 加共享锁
SELECT * FROM products WHERE id = 1 LOCK IN SHARE MODE;
-- 加排它锁
SELECT * FROM products WHERE id = 1 FOR UPDATE;仅对访问到的索引记录加锁,非索引条件会退化为表锁。
-- 索引字段,行锁生效
SELECT * FROM products WHERE id = 1001 FOR UPDATE;
-- 非索引字段,锁定整表
SELECT * FROM products WHERE name = 'iPhone' FOR UPDATE;防止幻读,锁定记录与相邻间隙。
-- 事务A
BEGIN;
UPDATE products SET stock = stock - 1 WHERE id = 1;
-- 等待事务B释放id=2
UPDATE products SET stock = stock - 1 WHERE id = 2;
-- 事务B
BEGIN;
UPDATE products SET stock = stock - 1 WHERE id = 2;
-- 等待事务A释放id=1
UPDATE products SET stock = stock - 1 WHERE id = 1;MySQL 自动检测死锁并回滚其中一个事务。
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;SHOW ENGINE INNODB STATUS\G从中可分析死锁信息、锁等待、事务运行时间等。
BEGIN;
-- 检查库存
SELECT stock FROM products WHERE id = 1001 FOR UPDATE;
-- 插入订单
INSERT INTO orders(user_id, product_id) VALUES (2001, 1001);
-- 扣减库存
UPDATE products SET stock = stock - 1 WHERE id = 1001;
COMMIT;使用 FOR UPDATE 锁定库存,避免超卖。
BEGIN;
UPDATE orders SET status = 'paid' WHERE id = 3001 AND status = 'unpaid';
INSERT INTO payments(order_id, amount) VALUES (3001, 99.99);
COMMIT;状态判断避免重复支付。
本项目适用于后台管理系统、电商用户中心、SaaS 用户模块等场景,特别适合开发者进行实战演练与面试准备。
我们将构建一个基础版的用户管理系统,具备以下业务功能:
涉及的核心业务对象包括:用户、角色、权限、日志等。
users)CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
status TINYINT DEFAULT 1 COMMENT '0:禁用, 1:启用',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);roles)CREATE TABLE roles (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL UNIQUE,
description VARCHAR(255)
);permissions)CREATE TABLE permissions (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL UNIQUE,
code VARCHAR(50) NOT NULL UNIQUE COMMENT '用于权限标识,如 user:view'
);user_role)CREATE TABLE user_role (
user_id INT,
role_id INT,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);role_permission)CREATE TABLE role_permission (
role_id INT,
permission_id INT,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id),
FOREIGN KEY (permission_id) REFERENCES permissions(id)
);login_logs)CREATE TABLE login_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
ip_address VARCHAR(45),
login_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);INSERT INTO roles(name, description) VALUES ('admin', '系统管理员'), ('user', '普通用户');
INSERT INTO permissions(name, code) VALUES
('查看用户', 'user:view'),
('新增用户', 'user:create'),
('删除用户', 'user:delete');
-- 分配权限给角色
INSERT INTO role_permission(role_id, permission_id) VALUES
(1, 1), (1, 2), (1, 3), -- admin 拥有全部权限
(2, 1); -- user 仅能查看用户INSERT INTO users(username, password, email) VALUES
('alice', 'hashed_pwd1', 'alice@example.com'),
('bob', 'hashed_pwd2', 'bob@example.com');
-- 分配角色
INSERT INTO user_role(user_id, role_id) VALUES
(1, 1), -- alice 为管理员
(2, 2); -- bob 为普通用户SELECT u.id, u.username, r.name AS role
FROM users u
JOIN user_role ur ON u.id = ur.user_id
JOIN roles r ON ur.role_id = r.id
WHERE u.status = 1;SELECT p.name, p.code
FROM users u
JOIN user_role ur ON u.id = ur.user_id
JOIN role_permission rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE u.username = 'alice';SELECT u.username, l.ip_address, l.login_time
FROM login_logs l
JOIN users u ON l.user_id = u.id
WHERE l.login_time >= NOW() - INTERVAL 7 DAY
ORDER BY l.login_time DESC;SELECT *
FROM users
WHERE username LIKE '%bob%'
ORDER BY created_at DESC
LIMIT 0, 10;在角色授权或用户注册等业务流程中,可以使用事务来确保数据完整性。
START TRANSACTION;
INSERT INTO users(username, password, email) VALUES('charlie', 'hashed_pwd3', 'charlie@example.com');
SET @uid = LAST_INSERT_ID();
INSERT INTO user_role(user_id, role_id) VALUES(@uid, 2); -- 默认赋普通角色
COMMIT;START TRANSACTION;
-- 假设某权限不存在导致失败
INSERT INTO role_permission(role_id, permission_id) VALUES(1, 999);
-- 失败时回滚
ROLLBACK;users.username:用于登录验证、搜索login_logs.user_id:日志查询user_role.user_id / role_permission.role_id:JOIN 优化CREATE INDEX idx_username ON users(username);
CREATE INDEX idx_user_log ON login_logs(user_id);EXPLAIN SELECT u.username, r.name FROM users u JOIN user_role ur ON u.id = ur.user_id JOIN roles r ON ur.role_id = r.id;可查看索引是否使用、JOIN 类型、Rows 扫描数量等。