SpringCloud电商项目数据库脚本拆解:用户、商品、订单三库表结构设计

发布时间:2026/10/3 8:53:42
SpringCloud电商项目数据库脚本拆解:用户、商品、订单三库表结构设计 简介面向基于SpringCloud的电商项目开发者与后端学习者这份资源以三个SQL脚本组成数据库设计方案集中解决用户、商品、订单三大核心模块的数据建模问题。压缩包共3个文件均为SQL脚本体积仅39KB内容覆盖用户登录与信息表、商品主表及分类属性表、订单详情与状态表等关键结构可直接导入MySQL进行项目开发或教学演示。资源已有275人学习适合需要快速搭建电商数据库或理解微服务数据拆分的初中级工程师。通过逐表阅读可以掌握用户身份验证、商品多维度管理、订单全流程跟踪的字段设计逻辑明确各服务独立库之间的关联方式为后续接口开发和业务扩展提供清晰、可落地的表结构参考。1. 一个SpringCloud电商项目数据库脚本先看表结构再写服务拿到这套shop_goods.sql、shop_user.sql、shop_order.sql三个脚本的时候第一反应是这项目把微服务最不该偷懒的地方做对了。很多SpringCloud电商项目翻车不是翻在网关超时、注册中心抖动而是翻在数据库层——用户订单对不上、商品库存查不到、跨服务join卡死。这套脚本把用户、商品、订单三条业务线拆成独立的数据域每个域对应一个微服务自己的库表结构设计清晰字段类型和索引也都给到位了。对准备搭SpringCloud电商Demo、做课程设计、或者想照着微服务拆分思路抄一份数据库建模作业的人来说这三个SQL文件可以直接拖进Navicat里跑不需要自己从零设计表。脚本里覆盖了用户注册登录、商品分类检索、订单从下单到完成的完整链路该有的表都齐了。下文按用户、商品、订单三个模块逐张拆表再讲怎么把它们映射到SpringCloud服务的工程里。2. 拆解三个SQL脚本user、goods、order三张核心表是怎么设计的2.1 shop_user.sql用户域的扩展字段设计思路用户域脚本先建用户主表。主表字段基本固定user_id自增主键username用户名password密码存的是加密后的密文长度给varchar(64)能放下bcrypt或SHA-256的结果email邮箱phone手机号status账号状态create_time注册时间update_time更新时间。下面截取关键建表语句CREATE TABLE user ( user_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 用户名, password VARCHAR(64) NOT NULL COMMENT 密码加密存储, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, status TINYINT NOT NULL DEFAULT 1 COMMENT 账号状态1正常0禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (user_id), UNIQUE KEY uk_username (username), KEY idx_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户主表;这里三个细节值得注意。phone用varchar而不是bigint是因为手机号超出Java的Integer范围用Long接收又可能在前端出现精度丢失varchar最省事。username建了唯一索引注册时直接靠数据库兜底防重应用层再快也有并发漏洞唯一索引才是最后一道防线。状态字段用TINYINT而不是varchar存正常/禁用查询性能好代码里用枚举映射显示文本。用户域还配了两张扩展表user_login记录登录日志字段有login_id、user_id、login_time、login_ip、device_type登录日志是持续增长的流水数据和主表混在一起会让主表越来越大查询变慢拆出来之后主表始终轻量。user_info存用户详细信息比如real_name、gender、birthday、address这些不是每次登录都要用的数据拆出去让主表只保留高频字段。两张扩展表都用user_id建普通索引查询某用户时走索引回表。2.2 shop_goods.sql商品与分类SPU/SKU的取舍商品域的脚本是三个脚本里表最多的因为商品数据天生多层。核心是goods商品主表字段包括goods_id、goods_name、category_id、price、stock、status上下架状态、sales销量、create_time。价格用DECIMAL(10,2)这是电商项目里必须注意的——价格不能用FLOAT或DOUBLE二进制浮点算总价会有0.10.2不等于0.3的经典问题DECIMAL是精确类型。CREATE TABLE goods ( goods_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 商品ID, goods_name VARCHAR(200) NOT NULL COMMENT 商品名称, category_id BIGINT NOT NULL COMMENT 分类ID, price DECIMAL(10,2) NOT NULL COMMENT 售价, stock INT NOT NULL DEFAULT 0 COMMENT 库存, status TINYINT NOT NULL DEFAULT 1 COMMENT 上下架状态1上架0下架, sales INT NOT NULL DEFAULT 0 COMMENT 销量, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (goods_id), KEY idx_category_status (category_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品主表;category_id和status建了联合索引因为商品列表页最常见的查询是某个分类下所有上架商品没这个索引MySQL就要全表扫。分类表category用parent_id字段实现树形结构顶级分类的parent_id为0普通商品挂到二级分类上。父分类和子分类用同一个表存储不需要单独设计层级表够用了。商品详情还拆了goods_detail表存description长文本和图片地址列表这些数据体积大一个商品描述可能几十KB散落在商品主表会让行溢出拖慢主表的全表扫描。如果需要管理更多商品规格型号goods_attribute表按attr_name、attr_value存属性键值对。这个脚本没做复杂的SPU/SKU多级模型商品本身就承担了SKU的角色。中小型电商项目的商品维度没有那么多硬套SPU-SKU模型反而让下单逻辑复杂表结构够用就行。2.3 shop_order.sql订单状态机与防超卖设计订单域是三张表里最见功力的。order订单主表不叫orders是因为ORDER是SQL关键字直接用会报语法错误。主表字段有order_id、order_no订单编号、user_id、total_amount、status、create_time、pay_time、ship_time、finish_time。订单状态用TINYINT整型存储对应关系固定0待支付、1已支付、2已发货、3已完成、4已关闭。CREATE TABLE order ( order_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 订单ID, order_no VARCHAR(32) NOT NULL COMMENT 订单编号, user_id BIGINT NOT NULL COMMENT 用户ID, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 订单状态0待支付1已支付2已发货3已完成4已关闭, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 下单时间, pay_time DATETIME DEFAULT NULL COMMENT 支付时间, ship_time DATETIME DEFAULT NULL COMMENT 发货时间, PRIMARY KEY (order_id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;order_no要单独建唯一索引因为订单号在前端和客服沟通里是高频查询条件而且必须全局唯一。user_id建索引是因为我的订单列表页永远按用户ID查没索引这个页面就是全表扫描。订单明细表order_detail字段有detail_id、order_id、goods_id、goods_name、price、num、total_price注意它把goods_name冗余了一份原理我在第3章细说。订单状态流转在表结构层面用status字段标记不建单独的定时任务表状态变更由后端服务在支付回调、发货操作时主动UPDATE。订单关闭逻辑靠定时任务扫描status0且create_time超过15分钟的记录这是电商项目的通用做法。3. 把脚本落进SpringCloud工程导入、分库与微服务映射3.1 建库与导入从Navicat到命令行拿到SQL文件先建数据库再导入因为脚本里没有CREATE DATABASE语句直接运行会报没选库的错误。打开Navicat新建三个数据库shop_user、shop_goods、shop_order字符集选utf8mb4排序规则选utf8mb4_general_ci。然后每个库右键运行SQL文件分别导入对应的脚本。命令行方式一样三条命令搞定mysql -uroot -p123456 shop_user shop_user.sql mysql -uroot -p123456 shop_goods shop_goods.sql mysql -uroot -p123456 shop_order shop_order.sql-u指定用户名-p后面直接跟密码把SQL文件重定向给mysql客户端执行。注意Windows下如果mysql命令没进PATH要先切到MySQL的bin目录。导入成功后可以用SHOW TABLES;验证每个库里应该能看到刚才拆表时提到的那些表。导入前可以用文本编辑器打开SQL文件看一眼头部确认里面有没有DROP TABLE IF EXISTS语句。有的话重复导入会先删旧表再建新表可以放心执行没有的话重复导入会报表已存在需要先手动清理。脚本里多数表都带了DROP TABLE IF EXISTS所以可以反复跑。3.2 微服务拆分与数据源隔离三个服务各管一张库SpringCloud项目的数据库层核心规则是一个微服务只能访问自己的数据库不能直连别人的库。下面这张表是这套脚本和微服务的标准映射关系微服务数据库核心表职责user-serviceshop_useruser、user_login、user_info注册、登录、用户信息goods-serviceshop_goodsgoods、category、goods_detail商品展示、库存管理order-serviceshop_orderorder、order_detail下单、订单查询、状态流转每个服务在application.yml里配自己的数据源user-service 只配shop_user的连接串order-service 只配shop_order的连接串。把三个服务的数据源配成同一个库是新手最容易犯的错表面看功能正常但违背了微服务拆分的初衷——一旦订单服务挂了用户服务也受牵连。服务之间不共享数据库表靠Feign或RestTemplate互相调用接口。比如用户登录后跳转商品页用户服务不查shop_goods库而是调用 goods-service 的HTTP接口拿到商品列表。这也是订单表不存完整用户信息的原因字符串类型的用户名在订单表里只存必要的关联ID。订单查询页面同时展示用户名和商品名表现层再调用两个服务接口聚合数据而不是在SQL里join三张表。3.3 从表到实体MyBatis-Plus映射时的外键处理建好表之后SpringBoot工程里用MyBatis-Plus接实体类重点在注解的写法。以商品实体为例Data TableName(goods) public class Goods { TableId(type IdType.AUTO) private Long goodsId; private String goodsName; private Long categoryId; private BigDecimal price; private Integer stock; private Integer status; }TableName(goods)指定实体对应表名TableId标记主键IdType.AUTO表示自增。注意categoryId是Long类型和表的BIGINT对应价格用了BigDecimal对应表的DECIMAL类型用Double接收价格在后续金额计算时会复现0.10.2的精度问题。表结构里没有建物理外键order表的user_id、goods表的category_id都是逻辑外键。微服务架构下物理外键会带来两个麻烦跨库无法建外键约束订单库和用户库是两个物理库删除被引用记录时数据库强制校验业务代码想先删主表再删详情表会报错。所以这套脚本只用索引维持关联关系不写FOREIGN KEY约束应用层自己在事务里控制数据一致性。4. 避坑与排查数据库层最容易翻车的五个地方4.1 现象服务重启后自增主键不连续有些项目启动时会预加载数据DELETE掉部分测试记录再插入新记录主键出现跳号。新手以为是bug其实自增主键就是这样——InnoDB的自增计数器一旦用了就不回退。原因是MySQL 8.0之前自增计数器存在内存里重启后通过MAX(auto_increment_col)1重新计算删掉最大记录后重启会复用旧值可能造成主键冲突。解决如果不需要对外暴露用户ID自增跳号无所谓如果订单号要连续就别用自增主键当订单号脚本里已经单独设计了order_no字段。生产环境建议把主键改成雪花ID或号段模式但课程设计和Demo用自增完全够。4.2 现象订单列表页查得慢SQL里join了三张表电商后台查订单详情直观写法是SELECT * FROM order JOIN user ON order.user_id user.user_id JOIN goods ON order_detail.goods_id goods.goods_id在单库环境下能跑拆成三个微服务之后连表都找不到——用户表在shop_user库订单表在shop_order库MySQL跨库join要走FEDERATED引擎配置麻烦而且性能极差。解决订单表和用户表之间只查ID拿到order表的user_id之后走Feign调用户服务批量查用户名和手机号订单明细里的goods_name在下单那一刻就冗余进了order_detail查订单详情不需要再调商品服务。这套脚本的冗余设计就是为了避免跨服务join。4.3 现象压测时数据库连接池报Connection is not available微服务每个实例默认的HikariCP连接池最大连接数是10三个服务共用一个MySQL实例时连接数轻松被打满。特别是下单接口同时操作订单表和扣库存一个事务要持有数据库连接更久并发一上来连接池就耗尽。解决按服务调整HikariCP参数订单服务压测时把最大连接数调到50左右用户服务30就够同时给MySQL设置合理的max_connections别让三个服务加起来超过MySQL上限。配置里把连接池的maximum-pool-size设大只是第一步连接泄漏才是隐藏炸弹——事务忘记提交或没释放连接连接池被慢慢耗尽。4.4 现象大促时偶发死锁日志里出现Deadlock found典型场景是用户同时下两笔订单两个事务都要操作同一行商品库存或者用户服务更新用户余额和订单服务更新订单状态按相反顺序加锁。死锁的本质是加锁顺序不一致一个事务先锁商品A再锁商品B另一个事务先锁B再锁A互相等对方释放。解决统一加锁顺序。更新多表时固定按主键ID升序锁行扣库存用一条原子UPDATEUPDATE goods SET stock stock - #{num} WHERE goods_id #{id} AND stock #{num}这条语句靠stock num条件在数据库层防超卖同时只锁一行死锁概率大幅下降。4.5 现象商品搜索页翻页到后面越来越慢goods表数据量到几十万行之后商品列表页的深翻页明显变慢。排查时发现商品名称和分类ID都有索引但列表页默认按sort字段排序而sort没有索引每次排序都要文件排序filesort深翻页还要回表取几万行的数据再丢弃。解决给高频排序字段sales和create_time建索引翻页从LIMIT 100000, 20改成WHERE id 100000 LIMIT 20走主键索引的区间扫描不回表。这套脚本的索引已经覆盖了基础查询场景但业务方如果新增了排序字段记得把索引补上这是数据库优化最实在的手段。5. 落地验证模拟一次完整下单流程看三份SQL表关系是否闭环拿到脚本别急着写代码先在数据库里手工跑一遍下单流程能验证表结构设计是否合理也能帮你熟悉每张表的用途。注册一个用户、查一件商品、生成订单、冻结库存四步操作正好覆盖三张脚本的核心表。-- 1. 注册用户 INSERT INTO user (username, password, email, phone) VALUES (test_user, e10adc3949ba59abbe56e057f20f883e, testexample.com, 13800138000); -- 2. 查询一件上架商品 SELECT goods_id, goods_name, price, stock FROM goods WHERE status 1 AND category_id 2; -- 3. 生成订单假设商品ID为1购买2件 INSERT INTO order (order_no, user_id, total_amount, status) VALUES (20250201001, 1, 199.98, 0); INSERT INTO order_detail (order_id, goods_id, goods_name, price, num, total_price) VALUES (1, 1, 示例商品, 99.99, 2, 199.98); -- 4. 扣减库存 UPDATE goods SET stock stock - 2 WHERE goods_id 1 AND stock 2;这段SQL执行成功说明用户表能写入账号、商品表能查到有效库存、订单表和明细表能建立关联、库存扣减逻辑走的是原子UPDATE。验证完顺手查一下订单详情确认order_detail里的goods_name和price是从商品表冗余过来的不是下单之后再join商品表取的——这个细节决定订单服务能不能独立的跑。我的习惯是拿到一份新项目的SQL脚本先在MySQL里把核心业务链路手工走一遍脚本跑通了再进工程代码。这样做过一遍之后哪个字段是哪个服务的、哪些索引覆盖了哪些查询心里基本有数了写Mapper的时候也不会摸黑。如果你要搭SpringCloud电商项目建议先把这三个库里表结构看清楚尤其是订单库的用户ID关联和商品库的库存扣减条件这两处是对微服务数据拆分理解最深的地方。希望这篇拆解帮到你。本文还有配套的精品资源点击获取