第二部分:数据库设计:地基决定上层建筑
2.1 为什么数据库设计如此重要?
在秒杀系统中,数据库设计直接影响系统的性能和可扩展性。糟糕的数据库设计会导致SQL执行缓慢、锁竞争严重、数据不一致等问题。
秒杀系统的数据库设计原则:
读写分离:秒杀活动时,读操作远多于写操作。可以通过主从复制实现读写分离。
热点隔离:秒杀商品表与普通商品表分开,避免热点数据影响其他业务。
冗余设计:订单表中冗余商品名称和价格,避免关联查询。
索引优化:为查询条件字段建立索引,避免全表扫描。
2.2 核心表结构详解
用户表(user)
用户表存储所有注册用户的信息。密码使用BCrypt加密存储,这是一种自适应哈希算法,即使数据库泄露,也很难还原出原始密码。
CREATE TABLE `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` VARCHAR(50) NOT NULL COMMENT '用户名(唯一)',
`password` VARCHAR(255) NOT NULL COMMENT '密码(BCrypt加密)',
`salt` VARCHAR(50) NOT NULL COMMENT '密码盐值',
`phone` VARCHAR(20) COMMENT '手机号(唯一)',
`email` VARCHAR(100) COMMENT '邮箱(唯一)',
`last_login_ip` VARCHAR(45) COMMENT '最后登录IP',
`last_login_time` DATETIME COMMENT '最后登录时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_phone` (`phone`),
UNIQUE KEY `uk_email` (`email`)
);
秒杀商品表(seckill_goods)
这是最核心的表,存储秒杀商品的信息。注意几个关键字段:
stock_count:库存数量,秒杀时会被频繁更新,是锁竞争的核心。
version:乐观锁版本号,用于无锁编程,避免行锁导致的性能下降。
start_time和end_time:秒杀时间段,需要建立索引以便快速过滤。
CREATE TABLE `seckill_goods` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`goods_name` VARCHAR(200) NOT NULL COMMENT '商品名称',
`price` DECIMAL(10,2) NOT NULL COMMENT '原价',
`seckill_price` DECIMAL(10,2) NOT NULL COMMENT '秒杀价(通常会便宜很多)',
`stock_count` INT NOT NULL COMMENT '库存数量',
`start_time` DATETIME NOT NULL COMMENT '秒杀开始时间',
`end_time` DATETIME NOT NULL COMMENT '秒杀结束时间',
`version` INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (`id`),
KEY `idx_start_end_time` (`start_time`, `end_time`)
);
秒杀订单表(seckill_order)
订单表记录了每个成功秒杀的用户和商品。注意几个设计细节:
order_no:订单号,使用唯一索引防止重复创建。
goods_price:冗余了商品价格,避免因商品价格变化影响历史订单。
order_status:订单状态,支持后续的支付、发货等流程。
CREATE TABLE `seckill_order` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`order_no` VARCHAR(32) NOT NULL COMMENT '订单号(唯一)',
`user_id` BIGINT NOT NULL,
`goods_id` BIGINT NOT NULL,
`goods_name` VARCHAR(200) NOT NULL,
`goods_price` DECIMAL(10,2) NOT NULL,
`quantity` INT NOT NULL DEFAULT 1,
`total_amount` DECIMAL(10,2) NOT NULL,
`order_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_goods_id` (`goods_id`)
);
秒杀记录表(seckill_record)
这是防重复秒杀的关键。通过uk_user_goods唯一索引,确保同一个用户对同一个商品只能秒杀一次。
CREATE TABLE `seckill_record` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`user_id` BIGINT NOT NULL,
`goods_id` BIGINT NOT NULL,
`order_id` BIGINT COMMENT '订单ID(成功时才有)',
`state` TINYINT NOT NULL DEFAULT 0 COMMENT '0处理中 1成功 2失败',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_goods` (`user_id`, `goods_id`)
);
2.3 数据库连接池配置:别让连接成为瓶颈
数据库连接池是应用程序与数据库之间的桥梁。每次创建数据库连接都是一次TCP握手,开销很大。连接池通过复用连接,大大减少了这种开销。
spring:
datasource:
hikari:
# 最大连接数:根据数据库服务器配置和应用需求确定
# 公式:连接数 = (核心数 * 2) + 磁盘数
# 单机MySQL通常建议20-50
maximum-pool-size: 20
# 最小空闲连接:连接池保持的最小连接数
# 设置太小会导致突发流量时创建连接,增加延迟
minimum-idle: 5
# 连接超时:获取连接的最大等待时间
# 设置太短会导致获取连接失败,太长会影响用户体验
connection-timeout: 30000
# 空闲超时:连接空闲多久后被回收
idle-timeout: 600000
# 最大生命周期:连接的最大存活时间
# 应该小于数据库的wait_timeout
max-lifetime: 1800000
# 连接测试查询:验证连接是否有效
connection-test-query: SELECT 1
为什么这些配置很重要?
maximum-pool-size过大:数据库连接数过多,会消耗数据库资源,导致性能下降。
maximum-pool-size过小:高并发时获取不到连接,请求排队或失败。
connection-timeout过大:线程长时间等待,拖垮整个系统。
max-lifetime过大:连接长时间不释放,可能被数据库端主动断开。
来源:
http://oplhc.cn/category/artificial-intelligence.html