下载工作台
Python 数据库实战

电商库表设计实战

试读上半部分 · 解锁后可读全文

第 7 章 · 电商库表设计实战

本章目标:在 db-demo 项目中完成 shop_db 电商核心库的完整 DDL walkthrough;逐表讲解 categoriesproductsusersordersorder_linescarts 的字段含义、外键约束索引策略;理解 slug + is_published + price(分) 字段约定;输出与 django-web ch05/ch07 的字段对照表,为 ch08~ch12 的 Python 访问层打基础。

学时建议:5~6 小时(含 2 小时 DDL 跟练与 ER 图绘制)

前置:完成 python-database ch01~ch06(SQL 查询、JOIN、事务、DDL、ER 与范式);了解基本电商业务名词。


7.1 场景说明:从 ER 图到可执行 DDL

ch06 已将 shop_db 抽象为 ER 模型。本章把模型落地为 MySQL 8 / SQLite 3 均可执行的 DDL,作为 db-demo 与后续 shop-db 毕业项目(ch17) 的 schema 基线。

业务实体表名与其它模块关系
商品分类categoriesdjango-web catalog.Category
商品productsdjango-web ch07+ Productslug/is_published
用户usersdjango-web accounts.User 简化版
订单ordersdjango-web ch05 Order
订单行order_linesdjango-web ch05 OrderLine
购物车carts / cart_itemspython-dev ch06 Cart 概念持久化
说明:教学项目路径 ~/python-learn/db-demo;数据库名 shop_db;对外 API 域名 https://api.example.com 仅为占位,不涉及任何企业内部仓库。

字段演进约定(全模块统一):

旧字段(ch01~ch06 练习)正式字段(ch07+)说明
skuslugURL 友好标识,如 wireless-mouse
is_activeis_published是否上架展示
price DECIMAL(10,2)price INTEGER(分)存储,避免浮点误差

7.2 全局 ER 关系图

┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│ categories  │ 1   N │  products   │ N   M │    tags     │  (ch10 扩展,本章预留)
│ id, slug    │◄──────│ category_id │       │  (可选)     │
└─────────────┘       └──────┬──────┘       └─────────────┘
                             │
         ┌───────────────────┼───────────────────┐
         │ N                 │ N                 │ N
         ▼                   ▼                   ▼
┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│ cart_items  │       │ order_lines │       │  (reviews)  │  ch17 扩展
│ cart_id     │       │ order_id    │       └─────────────┘
│ product_id  │       │ product_id  │
└──────┬──────┘       └──────┬──────┘
       │ N                 │ N
       ▼ 1                 ▼ 1
┌─────────────┐       ┌─────────────┐
│   carts     │       │   orders    │
│ user_id     │       │ user_id     │
└──────┬──────┘       └──────┬──────┘
       │ N                 │ N
       └─────────┬─────────┘
                 ▼ 1
          ┌─────────────┐
          │    users    │
          │ id, email   │
          └─────────────┘

关系摘要

关系类型外键位置ON DELETE 建议
Category → Product1:Nproducts.category_idRESTRICT(有商品禁删分类)
User → Order1:Norders.user_idRESTRICT
Order → OrderLine1:Norder_lines.order_idCASCADE
Product → OrderLine1:Norder_lines.product_idRESTRICT(保历史单价)
User → Cart1:1 或 1:Ncarts.user_id UNIQUECASCADE
Cart → CartItem1:Ncart_items.cart_idCASCADE
Product → CartItem1:Ncart_items.product_idRESTRICT

7.3 建库与字符集

MySQL

CREATE DATABASE IF NOT EXISTS shop_db
  DEFAULT CHARACTER SET utf8mb4
  DEFAULT COLLATE utf8mb4_unicode_ci;

USE shop_db;

SQLite(db-demo 默认):

mkdir -p ~/python-learn/db-demo/data
touch ~/python-learn/db-demo/data/shop_db.sqlite3

SQLite 无 CREATE DATABASE;文件即库。本章 DDL 以 SQLite 语法为主,MySQL 差异在 §7.10 说明。


7.4 表 1:categories(商品分类)

CREATE TABLE IF NOT EXISTS categories (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    name        TEXT    NOT NULL,
    slug        TEXT    NOT NULL UNIQUE,
    sort_order  INTEGER NOT NULL DEFAULT 0,
    created_at  TEXT    NOT NULL DEFAULT (datetime('now')),
    updated_at  TEXT    NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_categories_sort
    ON categories (sort_order, id);
字段类型约束说明
idINTEGERPK, AUTOsurrogate 主键
nameTEXTNOT NULL展示名,如「数码配件」
slugTEXTNOT NULL, UNIQUEURL 段,如 digital-accessories
sort_orderINTEGERDEFAULT 0前台排序,越小越靠前
created_atTEXTDEFAULT nowISO 时间字符串(SQLite);MySQL 用 DATETIME
updated_atTEXTDEFAULT now更新时间,应用层或触发器维护

设计要点

  • slug 全局唯一,供 /catalog/{slug}/ 路由使用(对照 django-web Category.slug)。
  • 不在分类表存商品数量;用 COUNT(*) 或冗余字段(ch13 优化再议)。

7.5 表 2:products(商品)

CREATE TABLE IF NOT EXISTS products (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    category_id   INTEGER NOT NULL,
    title         TEXT    NOT NULL,
    slug          TEXT    NOT NULL UNIQUE,
    description   TEXT,
    price         INTEGER NOT NULL CHECK (price >= 0),
    stock         INTEGER NOT NULL DEFAULT 0 CHECK (stock >= 0),
    is_published  INTEGER NOT NULL DEFAULT 0 CHECK (is_published IN (0, 1)),
    created_at    TEXT    NOT NULL DEFAULT (datetime('now')),
    updated_at    TEXT    NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (category_id) REFERENCES categories(id)
        ON DELETE RESTRICT ON UPDATE CASCADE
);

CREATE INDEX IF NOT EXISTS idx_products_category_published
    ON products (category_id, is_published, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_products_published_price
    ON products (is_published, price);
字段类型约束说明
category_idINTEGERFK → categories所属分类
titleTEXTNOT NULL商品标题
slugTEXTUNIQUE商品 URL 标识
descriptionTEXT可空详情 HTML/Markdown
priceINTEGER≥0单价(分),5990 = ¥59.90
stockINTEGER≥0可售库存
is_publishedINTEGER0/11=上架,0=草稿/下架
created_at / updated_atTEXT审计字段

价格(分)示例

展示价存储值 pricePython 转换
¥0.011price_cents / 100
¥59.905990round(yuan * 100)
¥199.0019900禁止用 float 直接存库

索引策略 §7.5.1

索引名服务查询
idx_products_category_published(category_id, is_published, created_at DESC)分类页已上架商品列表
idx_products_published_price(is_published, price)全站按价排序、筛选
slug UNIQUE隐式索引详情页 /products/{slug}/

7.6 表 3:users(用户)

CREATE TABLE IF NOT EXISTS users (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    email         TEXT    NOT NULL UNIQUE,
    password_hash TEXT    NOT NULL,
    nickname      TEXT    NOT NULL DEFAULT '',
    is_active     INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)),
    created_at    TEXT    NOT NULL DEFAULT (datetime('now')),
    updated_at    TEXT    NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_users_active
    ON users (is_active);
字段说明
email登录账号,唯一
password_hashbcrypt/argon2 摘要,禁止存明文
nickname展示名,如 user-demo
is_active0=禁用,1=正常
django-web 使用 Django 内置 User 模型字段更多(is_stafflast_login 等);本章为 db-demo 最小子集。

7.7 表 4:ordersorder_lines(订单)

7.7.1 orders

CREATE TABLE IF NOT EXISTS orders (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id     INTEGER NOT NULL,
    order_no    TEXT    NOT NULL UNIQUE,
    status      TEXT    NOT NULL DEFAULT 'pending'
                    CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled')),
    total_cents INTEGER NOT NULL DEFAULT 0 CHECK (total_cents >= 0),
    created_at  TEXT    NOT NULL DEFAULT (datetime('now')),
    updated_at  TEXT    NOT NULL DEFAULT (datetime('now')),
    FOREIGN KEY (user_id) REFERENCES users(id)
        ON DELETE RESTRICT ON UPDATE CASCADE
);

CREATE INDEX IF NOT EXISTS idx_orders_user_created
    ON orders (user_id, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_orders_status
    ON orders (status, created_at DESC);
字段说明
order_no业务单号,如 ORD-20260819-0001,唯一
status状态机:pending → paid → shipped;cancelled 终态
total_cents订单总金额(分),可与明细汇总校验

7.7.2 order_lines

CREATE TABLE IF NOT EXISTS order_lines (
    id           INTEGER PRIMARY KEY AUTOINCREMENT,
    order_id     INTEGER NOT NULL,
    product_id   INTEGER NOT NULL,
    quantity     INTEGER NOT NULL CHECK (quantity > 0),
    unit_price   INTEGER NOT NULL CHECK (unit_price >= 0),
    line_total   INTEGER NOT NULL CHECK (line_total >= 0),
    FOREIGN KEY (order_id) REFERENCES orders(id)

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。