Appearance
章节5:模块综合实战 — 电商平台数据库设计与分析
一、学习目标
完成本章实战后,你将能够:
- 独立完成电商数据库从 ER 设计到物理建表的全流程
- 严格遵循三范式原则建立表关系并合理反范式优化
- 使用窗口函数、聚合函数、CTE 对用户行为日志进行深度 SQL 分析
- 产出完整的数据库设计文档,包含 ER 图描述、表结构定义与索引设计
- 编写可复用的复杂业务 SQL 查询案例
二、实战项目概述
项目背景
设计一个轻量级电商平台的数据库,支持:
- 用户注册与管理
- 商品分类与商品管理
- 购物车与下单流程
- 订单与支付管理
- 用户行为日志记录与分析
需求分析
| 模块 | 核心需求 |
|---|---|
| 用户 | 注册 / 登录 / 个人资料管理 / 收货地址 |
| 商品 | 多级分类 / SPU + SKU 架构 / 上下架 |
| 购物车 | 增删改商品 / 合并结算 |
| 订单 | 创建订单 / 订单状态流转 / 订单明细 |
| 日志 | 浏览 / 搜索 / 加购 / 下单 行为记录 |
三、核心知识点
3.1 数据库设计(ER 图与三范式)
ER 图关系描述
用户 (User) ──1:N──> 收货地址 (Address)
用户 (User) ──1:N──> 购物车 (Cart)
用户 (User) ──1:N──> 订单 (Order)
商品分类 (Category) ──1:N──> 商品 (Product)
商品 (Product) ──1:N──> SKU (Sku)
购物车 (Cart) ──N:1──> SKU (Sku)
订单 (Order) ──1:N──> 订单明细 (OrderItem)
订单明细 (OrderItem) ──N:1──> SKU (Sku)
用户 (User) ──1:N──> 行为日志 (UserLog)
商品 (Product) ──1:N──> 行为日志 (UserLog)DDL 建表语句(完整设计)
sql
-- ==================== 创建数据库 ====================
CREATE DATABASE IF NOT EXISTS ecommerce
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
USE ecommerce;
-- ==================== 1. 用户表 ====================
CREATE TABLE `user` (
`id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT '用户ID',
`username` VARCHAR(32) NOT NULL COMMENT '用户名',
`password_hash` VARCHAR(128) NOT NULL COMMENT '密码哈希',
`phone` VARCHAR(15) DEFAULT NULL COMMENT '手机号',
`email` VARCHAR(64) DEFAULT NULL COMMENT '邮箱',
`nickname` VARCHAR(32) DEFAULT NULL COMMENT '昵称',
`avatar_url` VARCHAR(256) DEFAULT NULL COMMENT '头像',
`status` TINYINT DEFAULT 1 COMMENT '状态:1正常 0禁用',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_phone` (`phone`),
INDEX `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- ==================== 2. 收货地址表 ====================
CREATE TABLE `address` (
`id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT '地址ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`receiver` VARCHAR(32) NOT NULL COMMENT '收件人',
`phone` VARCHAR(15) NOT NULL COMMENT '联系电话',
`province` VARCHAR(20) NOT NULL COMMENT '省',
`city` VARCHAR(20) NOT NULL COMMENT '市',
`district` VARCHAR(20) NOT NULL COMMENT '区',
`detail` VARCHAR(200) NOT NULL COMMENT '详细地址',
`is_default` TINYINT DEFAULT 0 COMMENT '是否默认:1是 0否',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_user_id` (`user_id`),
CONSTRAINT `fk_address_user` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收货地址表';
-- ==================== 3. 商品分类表 ====================
CREATE TABLE `category` (
`id` INT UNSIGNED AUTO_INCREMENT COMMENT '分类ID',
`parent_id` INT UNSIGNED DEFAULT 0 COMMENT '父分类ID(0为顶级)',
`name` VARCHAR(50) NOT NULL COMMENT '分类名称',
`level` TINYINT DEFAULT 1 COMMENT '层级:1/2/3',
`sort_order` INT DEFAULT 0 COMMENT '排序',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_parent_id` (`parent_id`),
INDEX `idx_level` (`level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表';
-- ==================== 4. 商品表(SPU)====================
CREATE TABLE `product` (
`id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT '商品ID',
`category_id` INT UNSIGNED NOT NULL COMMENT '分类ID',
`name` VARCHAR(200) NOT NULL COMMENT '商品名称',
`title` VARCHAR(500) DEFAULT NULL COMMENT '商品标题/副标题',
`brand` VARCHAR(100) DEFAULT NULL COMMENT '品牌',
`description` TEXT COMMENT '商品描述',
`status` TINYINT DEFAULT 0 COMMENT '状态:0下架 1上架',
`sales_count` INT DEFAULT 0 COMMENT '销量',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_category_id` (`category_id`),
INDEX `idx_status` (`status`),
INDEX `idx_sales_count` (`sales_count`),
CONSTRAINT `fk_product_category` FOREIGN KEY (`category_id`) REFERENCES `category`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表(SPU)';
-- ==================== 5. SKU 表 ====================
CREATE TABLE `sku` (
`id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT 'SKU ID',
`product_id` BIGINT UNSIGNED NOT NULL COMMENT '所属商品ID',
`name` VARCHAR(200) NOT NULL COMMENT 'SKU名称(如 iPhone 14 128G 午夜色)',
`spec` JSON COMMENT '规格JSON(如 {"颜色":"午夜色","存储":"128G"})',
`price` DECIMAL(10,2) NOT NULL COMMENT '售价',
`original_price` DECIMAL(10,2) DEFAULT NULL COMMENT '原价',
`stock` INT DEFAULT 0 COMMENT '库存',
`image_url` VARCHAR(256) DEFAULT NULL COMMENT 'SKU图片',
`status` TINYINT DEFAULT 1 COMMENT '状态:1启用 0禁用',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_product_id` (`product_id`),
INDEX `idx_price` (`price`),
CONSTRAINT `fk_sku_product` FOREIGN KEY (`product_id`) REFERENCES `product`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='SKU表';
-- ==================== 6. 购物车表 ====================
CREATE TABLE `cart` (
`id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT '购物车ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`sku_id` BIGINT UNSIGNED NOT NULL COMMENT 'SKU ID',
`quantity` INT DEFAULT 1 COMMENT '数量',
`selected` TINYINT DEFAULT 1 COMMENT '是否选中:1是 0否',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_sku` (`user_id`, `sku_id`),
CONSTRAINT `fk_cart_user` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`),
CONSTRAINT `fk_cart_sku` FOREIGN KEY (`sku_id`) REFERENCES `sku`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车表';
-- ==================== 7. 订单表 ====================
CREATE TABLE `order` (
`id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT '订单ID',
`order_no` VARCHAR(32) NOT NULL COMMENT '订单编号',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`address_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '收货地址ID',
`total_amount` DECIMAL(12,2) DEFAULT 0 COMMENT '订单总金额',
`pay_amount` DECIMAL(12,2) DEFAULT 0 COMMENT '实付金额',
`pay_type` TINYINT DEFAULT NULL COMMENT '支付方式:1微信 2支付宝 3银行卡',
`status` TINYINT DEFAULT 0 COMMENT '状态:0待支付 1已支付 2已发货 3已完成 4已取消',
`pay_time` DATETIME DEFAULT NULL COMMENT '支付时间',
`delivery_time` DATETIME DEFAULT NULL COMMENT '发货时间',
`finish_time` DATETIME DEFAULT NULL COMMENT '完成时间',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_status` (`status`),
INDEX `idx_created_at` (`created_at`),
CONSTRAINT `fk_order_user` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
-- ==================== 8. 订单明细表 ====================
CREATE TABLE `order_item` (
`id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT '明细ID',
`order_id` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
`sku_id` BIGINT UNSIGNED NOT NULL COMMENT 'SKU ID',
`product_name` VARCHAR(200) NOT NULL COMMENT '商品名称(快照)',
`sku_name` VARCHAR(200) NOT NULL COMMENT 'SKU名称(快照)',
`price` DECIMAL(10,2) NOT NULL COMMENT '成交单价',
`quantity` INT NOT NULL COMMENT '数量',
`subtotal` DECIMAL(12,2) NOT NULL COMMENT '小计金额',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_order_id` (`order_id`),
CONSTRAINT `fk_item_order` FOREIGN KEY (`order_id`) REFERENCES `order`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';
-- ==================== 9. 用户行为日志表 ====================
CREATE TABLE `user_log` (
`id` BIGINT UNSIGNED AUTO_INCREMENT COMMENT '日志ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`product_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '商品ID(浏览/加购/下单关联)',
`action` VARCHAR(20) NOT NULL COMMENT '行为类型:view/search/add_cart/purchase',
`keyword` VARCHAR(200) DEFAULT NULL COMMENT '搜索关键词(search时)',
`ip_address` VARCHAR(45) DEFAULT NULL COMMENT '客户端IP',
`device` VARCHAR(50) DEFAULT NULL COMMENT '设备类型:PC/Mobile/App',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '行为时间',
PRIMARY KEY (`id`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_product_id` (`product_id`),
INDEX `idx_action` (`action`),
INDEX `idx_created_at` (`created_at`),
CONSTRAINT `fk_log_user` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户行为日志表';3.2 用户行为日志 SQL 分析
准备模拟数据
sql
-- 插入模拟日志数据(用于分析演示)
INSERT INTO user_log (user_id, product_id, action, keyword, device, created_at) VALUES
(1, 101, 'view', NULL, 'Mobile', '2024-06-01 10:00:00'),
(1, 102, 'view', NULL, 'Mobile', '2024-06-01 10:02:00'),
(1, 102, 'add_cart', NULL, 'Mobile', '2024-06-01 10:03:00'),
(1, NULL, 'search', '手机', 'Mobile', '2024-06-01 10:05:00'),
(1, 101, 'purchase', NULL, 'Mobile', '2024-06-01 10:10:00'),
(2, 201, 'view', NULL, 'PC', '2024-06-01 11:00:00'),
(2, 202, 'view', NULL, 'PC', '2024-06-01 11:01:00'),
(2, 203, 'view', NULL, 'PC', '2024-06-01 11:02:00'),
(2, NULL, 'search', '电脑', 'PC', '2024-06-01 11:05:00'),
(2, 201, 'add_cart', NULL, 'PC', '2024-06-01 11:06:00'),
(2, 201, 'purchase', NULL, 'PC', '2024-06-01 11:10:00'),
(3, 101, 'view', NULL, 'App', '2024-06-01 12:00:00'),
(3, 102, 'view', NULL, 'App', '2024-06-01 12:01:00'),
(3, 101, 'add_cart', NULL, 'App', '2024-06-01 12:02:00'),
(4, NULL, 'search', '耳机', 'Mobile', '2024-06-01 14:00:00');分析案例 1:用户行为漏斗分析
sql
-- 核心转化漏斗:浏览 → 加购 → 下单
WITH funnel AS (
SELECT
COUNT(DISTINCT CASE WHEN action = 'view' THEN user_id END) AS 浏览用户数,
COUNT(DISTINCT CASE WHEN action = 'add_cart' THEN user_id END) AS 加购用户数,
COUNT(DISTINCT CASE WHEN action = 'purchase' THEN user_id END) AS 下单用户数
FROM user_log
WHERE created_at >= '2024-06-01' AND created_at < '2024-07-01'
)
SELECT
浏览用户数,
加购用户数,
下单用户数,
CONCAT(ROUND(加购用户数 / 浏览用户数 * 100, 2), '%') AS 浏览到加购转化率,
CONCAT(ROUND(下单用户数 / 加购用户数 * 100, 2), '%') AS 加购到下单转化率
FROM funnel;分析案例 2:商品热度排行榜
sql
-- 使用窗口函数排行:被浏览最多的 Top 10 商品
WITH product_views AS (
SELECT
product_id,
COUNT(*) AS view_count,
COUNT(DISTINCT user_id) AS unique_users,
RANK() OVER (ORDER BY COUNT(*) DESC) AS rank_no
FROM user_log
WHERE action = 'view' AND product_id IS NOT NULL
GROUP BY product_id
)
SELECT
pv.rank_no,
pv.product_id,
p.name AS 商品名称,
pv.view_count AS 浏览次数,
pv.unique_users AS 独立访客数
FROM product_views pv
LEFT JOIN product p ON pv.product_id = p.id
WHERE pv.rank_no <= 10
ORDER BY pv.rank_no;分析案例 3:用户行为路径分析
sql
-- 每个用户的行为路径(按时间排序拼接)
SELECT
user_id,
GROUP_CONCAT(action ORDER BY created_at SEPARATOR ' → ') AS 行为路径
FROM user_log
WHERE created_at >= '2024-06-01'
GROUP BY user_id
ORDER BY user_id;
-- 使用窗口函数 LAG 查看用户上一个行为
SELECT
user_id,
action,
created_at,
LAG(action, 1) OVER (PARTITION BY user_id ORDER BY created_at) AS 上一个行为,
TIMESTAMPDIFF(SECOND,
LAG(created_at, 1) OVER (PARTITION BY user_id ORDER BY created_at),
created_at
) AS 时间间隔(秒)
FROM user_log
WHERE user_id = 1
ORDER BY created_at;分析案例 4:设备维度分析
sql
-- 各设备渠道的行为分布(使用 CTE + 行转列)
WITH device_stats AS (
SELECT
device,
action,
COUNT(*) AS cnt
FROM user_log
GROUP BY device, action
)
SELECT
device AS 设备类型,
MAX(CASE WHEN action = 'view' THEN cnt ELSE 0 END) AS 浏览,
MAX(CASE WHEN action = 'search' THEN cnt ELSE 0 END) AS 搜索,
MAX(CASE WHEN action = 'add_cart' THEN cnt ELSE 0 END) AS 加购,
MAX(CASE WHEN action = 'purchase' THEN cnt ELSE 0 END) AS 下单,
SUM(cnt) AS 总行为数
FROM device_stats
GROUP BY device
ORDER BY 总行为数 DESC;分析案例 5:搜索关键词分析
sql
-- 热搜关键词 Top 10
SELECT
keyword,
COUNT(*) AS search_count,
COUNT(DISTINCT user_id) AS search_users
FROM user_log
WHERE action = 'search' AND keyword IS NOT NULL
GROUP BY keyword
ORDER BY search_count DESC
LIMIT 10;
-- 搜索后转化分析:搜索某关键词的用户中有多少人最终购买了
WITH search_users AS (
SELECT DISTINCT user_id
FROM user_log
WHERE action = 'search' AND keyword = '手机'
),
purchase_users AS (
SELECT DISTINCT ul.user_id
FROM user_log ul
INNER JOIN search_users su ON ul.user_id = su.user_id
WHERE ul.action = 'purchase'
)
SELECT
(SELECT COUNT(*) FROM search_users) AS 搜索用户数,
(SELECT COUNT(*) FROM purchase_users) AS 购买用户数,
CONCAT(ROUND(
(SELECT COUNT(*) FROM purchase_users) * 100.0 /
NULLIF((SELECT COUNT(*) FROM search_users), 0), 2
), '%') AS 搜索转化率;3.3 综合实战:订单与商品分析
案例 6:用户复购率分析
sql
-- 统计每个用户的订单数,计算复购率
WITH user_orders AS (
SELECT
user_id,
COUNT(*) AS order_count
FROM `order`
WHERE status >= 1 -- 已支付及之后的状态
GROUP BY user_id
)
SELECT
order_count AS 订单数量,
COUNT(*) AS 用户数,
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM user_orders), 2) AS 占比百分比
FROM user_orders
GROUP BY order_count
ORDER BY order_count;
-- 复购率 = 下单次数 ≥ 2 的用户数 / 所有下单用户数
WITH user_stats AS (
SELECT
user_id,
COUNT(*) AS order_count,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) AS rn
FROM `order`
WHERE status >= 1
GROUP BY user_id
)
SELECT
COUNT(*) AS 总下单用户数,
SUM(CASE WHEN order_count >= 2 THEN 1 ELSE 0 END) AS 复购用户数,
CONCAT(ROUND(
SUM(CASE WHEN order_count >= 2 THEN 1 ELSE 0 END) * 100.0 /
COUNT(*), 2
), '%') AS 复购率
FROM user_stats;案例 7:RFM 用户价值分层
sql
-- RFM 模型(简化版):最近一次消费、消费频率、消费金额
WITH rfm_raw AS (
SELECT
user_id,
DATEDIFF('2024-07-01', MAX(created_at)) AS recency, -- R:距今天数
COUNT(*) AS frequency, -- F:订单数
SUM(pay_amount) AS monetary -- M:总消费金额
FROM `order`
WHERE status >= 1
GROUP BY user_id
),
rfm_score AS (
SELECT
user_id,
recency,
frequency,
monetary,
CASE
WHEN recency <= 7 THEN 5
WHEN recency <= 30 THEN 4
WHEN recency <= 90 THEN 3
WHEN recency <= 180 THEN 2
ELSE 1
END AS r_score,
CASE
WHEN frequency >= 10 THEN 5
WHEN frequency >= 5 THEN 4
WHEN frequency >= 3 THEN 3
WHEN frequency >= 1 THEN 2
ELSE 1
END AS f_score,
CASE
WHEN monetary >= 10000 THEN 5
WHEN monetary >= 5000 THEN 4
WHEN monetary >= 1000 THEN 3
WHEN monetary >= 500 THEN 2
ELSE 1
END AS m_score
FROM rfm_raw
)
SELECT
user_id,
CONCAT(r_score, f_score, m_score) AS rfm_code,
CASE
WHEN r_score >= 4 AND f_score >= 4 AND m_score >= 4 THEN '重要价值用户'
WHEN r_score >= 4 AND f_score >= 3 THEN '重要发展用户'
WHEN f_score >= 4 AND m_score >= 4 THEN '重要保持用户'
WHEN r_score <= 2 AND f_score <= 2 THEN '流失用户'
ELSE '一般用户'
END AS 用户分层
FROM rfm_score
ORDER BY r_score DESC, f_score DESC, m_score DESC;四、本章小结
| 知识点 | 关键要点 |
|---|---|
| 电商数据库设计 | 用户 → 商品(SPU/SKU)→ 购物车 → 订单 → 明细 → 日志,遵循三范式 |
| 索引设计 | 常用查询字段(user_id / status / created_at)建索引,联合索引遵循最左前缀 |
| 行为日志分析 | 用窗口函数做排名(RANK / ROW_NUMBER),用 CTE 构建漏斗模型 |
| RFM 分层 | Recency(最近消费) + Frequency(频率) + Monetary(金额) 三维度 |
| 转化率分析 | 漏斗分析法:浏览 → 加购 → 下单,逐步计算各环节转化率 |
五、综合练习
基础练习
- 根据上面提供的 DDL,在本地 MySQL 中完整创建
ecommerce数据库所有表 - 手动插入至少 5 个用户、10 个商品(各含 2 个 SKU)、20 条订单记录和 50 条行为日志
- 编写 SQL 查询:每个分类下销量最高的商品(使用窗口函数)
- 编写 SQL 查询:最近 30 天内未复购的用户列表(即只下过一次单且距今 > 30 天)
进阶练习
- 漏斗分析:使用 CTE 构建完整漏斗(浏览 → 加购 → 下单 → 付款 → 完成),计算每一步的绝对数和转化率
- 购物车弃单分析:查找将商品加入购物车但 24 小时内未下单的记录,统计弃单率最高的商品 Top 5
- 商品关联推荐:基于
order_item表,使用自关联查询购买商品 A 的用户同时购买商品 B 的频次(协同过滤基础版)
团队实战项目(可选)
项目任务:设计并实现一个小型直播带货平台的数据库系统
要求:
- 输出完整的 ER 图描述文档(可用 Mermaid 或 draw.io 绘制)
- 设计包含主播、直播间、商品、秒杀活动、打赏记录等表结构
- 编写至少 5 条有业务价值的分析 SQL(如:主播带货排行、直播间转化率、秒杀商品售罄时间分析)
- 使用 Python + pymysql 编写一个数据导入脚本,插入 10 万 + 级别的模拟数据