强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

MySQL 完全指南 / 第 23 章:实战场景

第 23 章:实战场景

学以致用。本章通过 4 个真实场景,将前面所有知识串联起来。


23.1 电商数据库设计

23.1.1 核心表结构

CREATE DATABASE IF NOT EXISTS ecommerce
    DEFAULT CHARACTER SET utf8mb4
    COLLATE utf8mb4_0900_ai_ci;

USE ecommerce;

-- ===================== 用户模块 =====================
CREATE TABLE users (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    phone VARCHAR(20) NOT NULL DEFAULT '',
    password_hash VARCHAR(255) NOT NULL,
    nickname VARCHAR(50) DEFAULT '',
    avatar_url VARCHAR(500) DEFAULT '',
    gender TINYINT UNSIGNED DEFAULT 0,
    birthday DATE DEFAULT NULL,
    status TINYINT UNSIGNED NOT NULL DEFAULT 1,
    last_login_at DATETIME DEFAULT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at DATETIME DEFAULT NULL,
    INDEX idx_phone (phone),
    INDEX idx_status (status),
    INDEX idx_created (created_at)
) ENGINE=InnoDB COMMENT='用户表';

-- ===================== 商品模块 =====================
CREATE TABLE categories (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    parent_id INT UNSIGNED DEFAULT 0,
    name VARCHAR(100) NOT NULL,
    sort_order INT DEFAULT 0,
    status TINYINT DEFAULT 1,
    INDEX idx_parent (parent_id)
) ENGINE=InnoDB COMMENT='商品分类表';

CREATE TABLE products (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    category_id INT UNSIGNED NOT NULL,
    name VARCHAR(200) NOT NULL,
    subtitle VARCHAR(500) DEFAULT '',
    main_image VARCHAR(500) DEFAULT '',
    price DECIMAL(10,2) NOT NULL COMMENT '售价',
    cost_price DECIMAL(10,2) DEFAULT NULL COMMENT '成本价',
    stock INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '库存',
    sales INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '销量',
    status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '1=上架 0=下架',
    attributes JSON DEFAULT NULL COMMENT '商品属性',
    detail TEXT DEFAULT NULL COMMENT '商品详情',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at DATETIME DEFAULT NULL,
    INDEX idx_category (category_id),
    INDEX idx_status_sales (status, sales),
    FULLTEXT INDEX ft_name (name)
) ENGINE=InnoDB COMMENT='商品表';

CREATE TABLE product_skus (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    product_id BIGINT UNSIGNED NOT NULL,
    sku_code VARCHAR(50) NOT NULL UNIQUE COMMENT 'SKU编码',
    spec_values JSON NOT NULL COMMENT '规格值,如{"颜色":"红","尺码":"M"}',
    price DECIMAL(10,2) NOT NULL,
    stock INT UNSIGNED NOT NULL DEFAULT 0,
    INDEX idx_product (product_id)
) ENGINE=InnoDB COMMENT='商品SKU表';

-- ===================== 订单模块 =====================
CREATE TABLE orders (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(32) NOT NULL UNIQUE,
    user_id BIGINT UNSIGNED NOT NULL,
    total_amount DECIMAL(12,2) NOT NULL,
    pay_amount DECIMAL(12,2) NOT NULL,
    freight_amount DECIMAL(10,2) DEFAULT 0,
    discount_amount DECIMAL(12,2) DEFAULT 0,
    status TINYINT UNSIGNED NOT NULL DEFAULT 0
        COMMENT '0=待支付 1=已支付 2=已发货 3=已完成 4=已取消 5=已退款',
    pay_type TINYINT UNSIGNED DEFAULT NULL COMMENT '1=微信 2=支付宝 3=银行卡',
    pay_time DATETIME DEFAULT NULL,
    ship_time DATETIME DEFAULT NULL,
    receive_time DATETIME DEFAULT NULL,
    receiver_name VARCHAR(50) NOT NULL,
    receiver_phone VARCHAR(20) NOT NULL,
    receiver_address VARCHAR(500) NOT NULL,
    remark VARCHAR(500) DEFAULT '',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at DATETIME DEFAULT NULL,
    INDEX idx_user_id (user_id),
    INDEX idx_status (status),
    INDEX idx_created (created_at),
    INDEX idx_user_status (user_id, status)
) ENGINE=InnoDB COMMENT='订单表';

CREATE TABLE order_items (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    order_id BIGINT UNSIGNED NOT NULL,
    product_id BIGINT UNSIGNED NOT NULL,
    sku_id BIGINT UNSIGNED DEFAULT NULL,
    product_name VARCHAR(200) NOT NULL,
    sku_spec VARCHAR(200) DEFAULT '',
    price DECIMAL(10,2) NOT NULL,
    quantity INT UNSIGNED NOT NULL,
    total_amount DECIMAL(12,2) NOT NULL,
    INDEX idx_order_id (order_id),
    INDEX idx_product_id (product_id)
) ENGINE=InnoDB COMMENT='订单商品表';

23.1.2 核心业务查询

-- 查询用户的订单列表(分页)
SELECT o.id, o.order_no, o.total_amount, o.status, o.created_at,
       GROUP_CONCAT(oi.product_name SEPARATOR ', ') AS products
FROM orders o
LEFT JOIN order_items oi ON o.id = oi.order_id
WHERE o.user_id = 100 AND o.deleted_at IS NULL
GROUP BY o.id
ORDER BY o.created_at DESC
LIMIT 20 OFFSET 0;

-- 查询商品排行榜
SELECT p.id, p.name, p.price, p.sales, p.main_image
FROM products p
WHERE p.status = 1 AND p.deleted_at IS NULL
ORDER BY p.sales DESC
LIMIT 50;

-- 统计某月销售数据
SELECT 
    DATE(o.created_at) AS sale_date,
    COUNT(*) AS order_count,
    SUM(o.pay_amount) AS revenue,
    COUNT(DISTINCT o.user_id) AS unique_buyers
FROM orders o
WHERE o.status IN (1, 2, 3)
  AND o.created_at >= '2026-05-01' AND o.created_at < '2026-06-01'
GROUP BY DATE(o.created_at)
ORDER BY sale_date;

23.1.3 库存扣减(乐观锁)

-- 乐观锁扣减库存
UPDATE product_skus 
SET stock = stock - 1 
WHERE id = 1001 AND stock > 0;
-- 检查 affected_rows,如果为 0 则库存不足

23.2 社交关系系统

23.2.1 表设计

-- 关注关系表
CREATE TABLE follows (
    follower_id BIGINT UNSIGNED NOT NULL COMMENT '关注者',
    following_id BIGINT UNSIGNED NOT NULL COMMENT '被关注者',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (follower_id, following_id),
    INDEX idx_following (following_id, follower_id)
) ENGINE=InnoDB COMMENT='关注关系表';

-- 好友关系表(双向)
CREATE TABLE friendships (
    user_id BIGINT UNSIGNED NOT NULL,
    friend_id BIGINT UNSIGNED NOT NULL,
    status TINYINT UNSIGNED DEFAULT 1 COMMENT '1=正常 0=拉黑',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, friend_id),
    INDEX idx_friend (friend_id)
) ENGINE=InnoDB COMMENT='好友关系表';

-- 动态表
CREATE TABLE posts (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    content TEXT NOT NULL,
    images JSON DEFAULT NULL,
    like_count INT UNSIGNED DEFAULT 0,
    comment_count INT UNSIGNED DEFAULT 0,
    visibility TINYINT DEFAULT 1 COMMENT '1=公开 2=仅好友 3=仅自己',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user_time (user_id, created_at DESC),
    INDEX idx_created (created_at DESC)
) ENGINE=InnoDB COMMENT='动态表';

-- 点赞表
CREATE TABLE likes (
    user_id BIGINT UNSIGNED NOT NULL,
    target_type TINYINT UNSIGNED NOT NULL COMMENT '1=动态 2=评论',
    target_id BIGINT UNSIGNED NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, target_type, target_id),
    INDEX idx_target (target_type, target_id)
) ENGINE=InnoDB COMMENT='点赞表';

23.2.2 核心查询

-- 查询某用户的粉丝数
SELECT COUNT(*) AS follower_count FROM follows WHERE following_id = 100;

-- 查询某用户的关注数
SELECT COUNT(*) AS following_count FROM follows WHERE follower_id = 100;

-- 查询共同关注
SELECT f1.following_id
FROM follows f1
INNER JOIN follows f2 
    ON f1.following_id = f2.following_id
WHERE f1.follower_id = 100 AND f2.follower_id = 200;

-- 查询关注的人的动态(信息流)
SELECT p.*
FROM posts p
INNER JOIN follows f ON p.user_id = f.following_id
WHERE f.follower_id = 100
  AND p.visibility = 1
ORDER BY p.created_at DESC
LIMIT 50;

-- 推荐可能认识的人(朋友的朋友,但还不是自己的朋友)
SELECT DISTINCT f2.following_id
FROM follows f1
INNER JOIN follows f2 ON f1.following_id = f2.follower_id
WHERE f1.follower_id = 100
  AND f2.following_id != 100
  AND f2.following_id NOT IN (
      SELECT following_id FROM follows WHERE follower_id = 100
  )
LIMIT 20;

23.3 日志系统设计

23.3.1 表设计

-- 操作日志表(分区)
CREATE TABLE operation_logs (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    user_id BIGINT UNSIGNED DEFAULT NULL,
    action VARCHAR(50) NOT NULL COMMENT '操作类型',
    resource_type VARCHAR(50) NOT NULL COMMENT '资源类型',
    resource_id VARCHAR(50) DEFAULT NULL,
    detail JSON DEFAULT NULL COMMENT '操作详情',
    ip_address VARCHAR(45) DEFAULT NULL,
    user_agent VARCHAR(500) DEFAULT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id, created_at),
    INDEX idx_user_id (user_id),
    INDEX idx_action (action),
    INDEX idx_resource (resource_type, resource_id)
) ENGINE=InnoDB
PARTITION BY RANGE (TO_DAYS(created_at)) (
    PARTITION p202601 VALUES LESS THAN (TO_DAYS('2026-02-01')),
    PARTITION p202602 VALUES LESS THAN (TO_DAYS('2026-03-01')),
    PARTITION p202603 VALUES LESS THAN (TO_DAYS('2026-04-01')),
    PARTITION p202604 VALUES LESS THAN (TO_DAYS('2026-05-01')),
    PARTITION p202605 VALUES LESS THAN (TO_DAYS('2026-06-01')),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

-- 统计每日操作量
SELECT 
    DATE(created_at) AS day,
    action,
    COUNT(*) AS cnt
FROM operation_logs
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
GROUP BY DATE(created_at), action
ORDER BY day DESC, cnt DESC;

-- 清理旧分区(比 DELETE 快)
ALTER TABLE operation_logs DROP PARTITION p202601;

23.3.2 Binlog 实时解析

# 使用 mysqlbinlog 实时解析
mysqlbinlog --base64-output=DECODE-ROWS -v --start-datetime="2026-05-10 00:00:00" \
  mysql-bin.000010 | grep -E "### (INSERT|UPDATE|DELETE)" | head -20

23.4 缓存策略

23.4.1 Cache-Aside 模式

读请求:
1. 先查 Redis 缓存
2. 命中 → 返回
3. 未命中 → 查 MySQL → 写入 Redis → 返回

写请求:
1. 更新 MySQL
2. 删除 Redis 缓存(不是更新缓存)
import redis
import pymysql

r = redis.Redis()
db = pymysql.connect(host='localhost', user='root', password='pass', db='myapp')

def get_user(user_id):
    # 1. 先查缓存
    cache_key = f"user:{user_id}"
    cached = r.get(cache_key)
    if cached:
        return json.loads(cached)
    
    # 2. 查数据库
    cursor = db.cursor(pymysql.cursors.DictCursor)
    cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
    user = cursor.fetchone()
    
    if user:
        # 3. 写入缓存(设置过期时间)
        r.setex(cache_key, 3600, json.dumps(user, default=str))
    
    return user

def update_user(user_id, data):
    # 1. 更新数据库
    cursor = db.cursor()
    cursor.execute("UPDATE users SET nickname = %s WHERE id = %s", 
                   (data['nickname'], user_id))
    db.commit()
    
    # 2. 删除缓存
    r.delete(f"user:{user_id}")

23.4.2 缓存一致性问题

问题原因解决方案
缓存穿透查询不存在的数据布隆过滤器 / 缓存空值
缓存击穿热点 key 过期,并发查 DB互斥锁 / 永不过期 + 异步更新
缓存雪崩大量 key 同时过期过期时间加随机值
-- 缓存空值示例
-- 查询不存在的用户
SELECT * FROM users WHERE id = 999999;
-- 返回 NULL,缓存空值 5 分钟
-- r.setex("user:999999", 300, "NULL")

23.5 综合架构示例

23.5.1 中型电商系统架构

                    用户请求
                       │
                   ┌───▼───┐
                   │ Nginx  │
                   └───┬───┘
                       │
              ┌────────▼────────┐
              │   应用服务集群    │
              │  (Spring Boot)   │
              └──┬──────┬───────┘
                 │      │
           ┌─────▼─┐ ┌──▼───┐
           │ Redis  │ │ MySQL│
           │ 缓存   │ │ 主从  │
           └───────┘ └──────┘
                        │
              ┌─────────▼─────────┐
              │  ProxySQL / MySQL  │
              │  Router            │
              └─────────┬─────────┘
                        │
           ┌────────────┼────────────┐
           │            │            │
      ┌────▼────┐ ┌────▼────┐ ┌────▼────┐
      │ Master  │ │ Slave 1 │ │ Slave 2 │
      │ (写)    │ │ (读)    │ │ (读)    │
      └─────────┘ └─────────┘ └─────────┘

23.5.2 关键配置总结

组件配置说明
MySQL Masterinnodb_buffer_pool_size=10G, sync_binlog=1写性能+安全
MySQL Slaveread_only=ON, replica_parallel_workers=8读性能+安全
ProxySQL读写分离规则自动路由
RedisCache-Aside 模式减少数据库压力
应用HikariCP 连接池, 最大 20 连接/实例连接管理
备份xtrabackup 每日全量 + binlog 增量数据安全
监控Prometheus + Grafana实时告警

23.6 学习路线图

入门(1-2 周)
├── SQL 基础(第 4 章)
├── 安装配置(第 2 章)
└── 数据类型(第 6 章)

进阶(2-4 周)
├── 索引原理(第 7 章)
├── 查询优化(第 8 章)
├── 事务与锁(第 10 章)
└── 表设计(第 9 章)

中级(1-2 月)
├── 架构原理(第 3 章)
├── 存储过程(第 11 章)
├── 主从复制(第 12 章)
└── 备份恢复(第 13 章)

高级(2-3 月)
├── 高可用方案(第 16 章)
├── 分库分表(第 17 章)
├── 监控体系(第 15 章)
└── 安全管理(第 14 章)

专家(持续)
├── 容器化部署(第 18 章)
├── 数据迁移(第 19 章)
├── 性能测试(第 20 章)
└── 实战积累(第 23 章)

业务场景

场景 1:从零搭建电商数据库

1. 需求分析 → 确定实体和关系
2. ER 图设计 → 用户、商品、订单、支付
3. 表结构设计 → 遵循规范
4. 索引设计 → 针对核心查询
5. 测试数据 → 生成百万级测试数据
6. 性能测试 → sysbench 验证
7. 优化 → EXPLAIN 慢查询
8. 上线 → 配置监控告警
9. 迭代 → 根据业务增长调整

扩展阅读


🎉 恭喜你完成了 MySQL 完全指南的全部 23 章!

学习数据库是一个持续的过程。建议结合实际项目不断实践,遇到问题回到对应的章节查阅。

记住:没有完美的数据库设计,只有最适合业务的设计。