准备表
-- 商品表:存储商品信息
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CHECK (price >= 0),
CHECK (stock >= 0))CHARSET=UTF8;
-- 订单表:存储订单基本信息
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CHECK (total_amount >= 0))CHARSET=UTF8;
-- 支付表:存储支付详情
CREATE TABLE payments (
payment_id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
payment_method ENUM('credit_card', 'alipay', 'wechat', 'cash') NOT NULL,
payment_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE,
CHECK (amount >= 0))CHARSET=UTF8;
-- 用户积分表:用于案例中的积分更新操作
CREATE TABLE user_points (
user_id INT PRIMARY KEY,
points INT NOT NULL DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CHECK (points >= 0))CHARSET=UTF8;
-- 向商品表插入初始数据(案例中操作了product_id=101的商品)
INSERT INTO products (product_id, product_name, price, stock)
VALUES
(101, '无线蓝牙耳机', 149.99, 50), -- 案例中会扣减该商品库存
(102, '智能手表', 299.99, 30),
(103, '便携式充电宝', 59.99, 100);
-- 向用户积分表插入数据(案例中操作了user_id=501的用户)
INSERT INTO user_points (user_id, points)
VALUES
(501, 1000), -- 案例中会为该用户增加积分
(502, 500),
(503, 2000);
-- 初始订单表和支付表可以为空
-- 因为案例中会创建新的订单和支付记录
-- 这里插入一条历史订单作为示例(不会影响案例操作)
INSERT INTO orders (order_id, user_id, total_amount, status)
VALUES (1000, 502, 89.99, 'delivered');
INSERT INTO payments (payment_id, order_id, amount, payment_method)
VALUES (2000, 1000, 89.99, 'wechat');
-- 1. 关闭自动提交模式
SHOW VARIABLES LIKE 'autocommit';
SET AUTOCOMMIT = 0;
-- 2. 开始新事务
START TRANSACTION;
-- 模拟订单处理流程
-- 步骤1: 扣减商品库存
UPDATE products
SET stock = stock - 2
WHERE product_id = 101;
开始案例
-- 设置第一个保存点
SAVEPOINT after_stock_update;
-- 步骤2: 创建订单记录
INSERT INTO orders (order_id, user_id, total_amount, status)
VALUES (1001, 501, 299.98, 'pending');
-- 设置第二个保存点
SAVEPOINT after_order_create;
-- 步骤3: 记录支付信息
INSERT INTO payments (payment_id, order_id, amount, payment_method)
VALUES (2001, 1001, 299.98, 'credit_card');
-- 假设这里发现支付方式错误,需要回滚到创建订单之后
ROLLBACK TO SAVEPOINT after_order_create;
-- 修正后重新记录支付信息
INSERT INTO payments (payment_id, order_id, amount, payment_method)
VALUES (2001, 1001, 299.98, 'alipay');
-- 释放不再需要的保存点
RELEASE SAVEPOINT after_stock_update;
-- 步骤4: 更新用户积分
UPDATE user_points
SET points = points + 299
WHERE user_id = 501;
-- 设置第三个保存点
SAVEPOINT after_points_update;
-- 检查所有操作无误后提交事务
COMMIT;
-- 假设发现积分计算错误,需要回滚所有操作
-- ROLLBACK;
-- 恢复自动提交模式
SET AUTOCOMMIT = 1;
REDO 的工作过程
当事务对数据进行修改时(如 INSERT、UPDATE),数据库会先将修改操作记录到REDO 日志缓冲区(内存区域),同时在内存中的数据页(Buffer Pool)中执行实际修改。事务提交前,数据库会先将 REDO 日志从缓冲区强制刷写到磁盘上的 REDO 日志文件
目的:即使事务修改的数据还未从内存刷到磁盘(如数据文件),只要日志已持久化,崩溃后仍可通过日志恢复。
当数据库重启时,会启动恢复进程,扫描 REDO 日志文件:对于所有已提交但数据未刷盘的事务,根据日志内容重新执行修改操作(即 “重做”),将数据更新到磁盘;UNDO 的工作过程
当事务对数据进行修改时(如 UPDATE、DELETE),数据库会先将修改前的旧值记录到 UNDO 日志中(通常存储在内存缓冲区),再执行实际的修改操作(更新内存数据页)主动回滚:当用户执行ROLLBACK命令时,数据库会根据当前事务的 UNDO 日志,反向执行操作,
直至事务所有修改被撤销。崩溃恢复:数据库重启时,对于未提交的事务(包括崩溃时正在执行的事务),恢复进程会扫描 UNDO 日志,
撤销其所有修改,确保这些未完成的事务不会对数据库留下 “脏数据”。
排他锁(简称 X 锁):事务 A 对数据加 X 锁后,其他事务既不能加 X 锁(无法修改),也不能加共享锁
保证数据修改操作的独占性,防止多个事务同时修改同一资源。共享锁(简称 S 锁):事务 A 对数据加 S 锁后,其他事务可加 S 锁(共同读取),但不能加 X 锁(需等待 S 锁释放)。
允许多个事务同时读取同一资源,避免读操作被写操作阻塞
评论 (0)