【1.6 漫画数据库设计实战 - 从零开始设计高性能数据库】
1.6 漫画数据库设计实战 - 从零开始设计高性能数据库
🎯 学习目标
- 掌握数据库表结构设计原则
- 理解字段类型选择与优化
- 学会雪花算法ID生成策略
- 掌握索引设计与优化技巧
- 了解分库分表设计方案
📖 故事开始
小明: “老王,我总是不知道怎么设计数据库表,字段类型该选什么,索引怎么建?”
架构师老王: “哈哈,数据库设计就像盖房子,地基不牢,地动山摇!今天我们从头开始学习数据库设计的艺术。”
小明: “那从哪里开始呢?”
架构师老王: “先从一个电商系统的用户表开始…”
🏗️ 第一章:表结构设计原则
1.1 三大范式与反范式
架构师老王: “数据库设计有三大范式,但实际项目中我们经常需要反范式设计。”
-- 第一范式:原子性(每个字段不可再分)
-- ❌ 错误设计
CREATE TABLE user_bad (id BIGINT PRIMARY KEY,name VARCHAR(100),address TEXT -- 包含省市区,违反第一范式
);-- ✅ 正确设计
CREATE TABLE user_good (id BIGINT PRIMARY KEY,name VARCHAR(100),province VARCHAR(50),city VARCHAR(50),district VARCHAR(50),detail_address VARCHAR(200)
);-- 第二范式:完全函数依赖
-- ❌ 错误设计
CREATE TABLE order_item_bad (order_id BIGINT,product_id BIGINT,product_name VARCHAR(100), -- 依赖于product_id,不依赖于组合主键quantity INT,price DECIMAL(10,2),PRIMARY KEY (order_id, product_id)
);-- ✅ 正确设计
CREATE TABLE order_item_good (order_id BIGINT,product_id BIGINT,quantity INT,price DECIMAL(10,2),PRIMARY KEY (order_id, product_id)
);-- 第三范式:消除传递依赖
-- ❌ 错误设计
CREATE TABLE employee_bad (id BIGINT PRIMARY KEY,name VARCHAR(100),department_id BIGINT,department_name VARCHAR(100), -- 传递依赖于department_idsalary DECIMAL(10,2)
);-- ✅ 正确设计
CREATE TABLE employee_good (id BIGINT PRIMARY KEY,name VARCHAR(100),department_id BIGINT,salary DECIMAL(10,2)
);CREATE TABLE department (id BIGINT PRIMARY KEY,name VARCHAR(100)
);
1.2 反范式设计场景
-- 电商订单表 - 为了查询性能,适当冗余
CREATE TABLE orders (id BIGINT PRIMARY KEY,user_id BIGINT NOT NULL,user_name VARCHAR(100), -- 冗余用户名,避免关联查询user_phone VARCHAR(20), -- 冗余手机号total_amount DECIMAL(12,2),item_count INT, -- 冗余商品数量status TINYINT DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,INDEX idx_user_id (user_id),INDEX idx_status_created (status, created_at),INDEX idx_created_at (created_at)
);-- 商品表 - 冗余分类信息
CREATE TABLE products (id BIGINT PRIMARY KEY,name VARCHAR(200) NOT NULL,category_id BIGINT,category_name VARCHAR(100), -- 冗余分类名brand_id BIGINT,brand_name VARCHAR(100), -- 冗余品牌名price DECIMAL(10,2),stock INT DEFAULT 0,sales_count INT DEFAULT 0, -- 冗余销量统计created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,INDEX idx_category (category_id),INDEX idx_brand (brand_id),INDEX idx_price (price),INDEX idx_sales (sales_count DESC)
);
🔢 第二章:字段类型选择与优化
2.1 数值类型选择
架构师老王: “选择合适的数据类型,既能节省存储空间,又能提高查询性能。”
-- 数值类型选择指南
CREATE TABLE type_examples (-- 主键:使用BIGINT,支持雪花算法id BIGINT UNSIGNED PRIMARY KEY,-- 状态字段:使用TINYINTstatus TINYINT UNSIGNED DEFAULT 0 COMMENT '0:待支付 1:已支付 2:已发货 3:已完成',-- 年龄:使用TINYINT UNSIGNED (0-255)age TINYINT UNSIGNED,-- 计数器:根据预期大小选择view_count INT UNSIGNED DEFAULT 0, -- 浏览量like_count MEDIUMINT UNSIGNED DEFAULT 0, -- 点赞数-- 金额:使用DECIMAL,避免浮点精度问题price DECIMAL(10,2) NOT NULL COMMENT '价格,精确到分',balance DECIMAL(15,2) DEFAULT 0.00 COMMENT '余额',-- 百分比:可以存储为整数(乘以100)discount_rate SMALLINT UNSIGNED COMMENT '折扣率,如85表示8.5折',-- 时间戳:根据需求选择created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,expired_at DATETIME COMMENT '过期时间'
);
2.2 字符串类型优化
-- 字符串类型选择
CREATE TABLE string_examples (id BIGINT PRIMARY KEY,-- 固定长度:使用CHARcountry_code CHAR(2) COMMENT '国家代码 CN/US',gender CHAR(1) COMMENT '性别 M/F',-- 变长字符串:使用VARCHARusername VARCHAR(50) NOT NULL COMMENT '用户名',email VARCHAR(100) COMMENT '邮箱',phone VARCHAR(20) COMMENT '手机号',-- 长文本:使用TEXTdescription TEXT COMMENT '商品描述',content LONGTEXT COMMENT '文章内容',-- JSON数据:MySQL 5.7+支持JSON类型extra_info JSON COMMENT '扩展信息',-- 枚举类型:适合固定选项priority ENUM('low', 'medium', 'high') DEFAULT 'medium',INDEX idx_username (username),INDEX idx_email (email),INDEX idx_phone (phone)
);-- 字符串长度优化示例
CREATE TABLE user_profiles (user_id BIGINT PRIMARY KEY,nickname VARCHAR(50), -- 昵称最多50字符avatar_url VARCHAR(500), -- 头像URLbio VARCHAR(500), -- 个人简介location VARCHAR(100), -- 地理位置website VARCHAR(200), -- 个人网站-- 使用前缀索引优化长字符串INDEX idx_avatar_prefix (avatar_url(100)),INDEX idx_bio_prefix (bio(50))
);
❄️ 第三章:雪花算法ID生成策略
3.1 雪花算法原理
架构师老王: “雪花算法生成的ID是64位长整型,包含时间戳、机器ID和序列号。”
雪花算法ID结构(64位):
┌─────────────────────────────────────────────────┬──────────┬──────────┬──────────────┐
│ 时间戳(41位) │机器ID(10位)│ 序列号(12位) │ 符号位(1位) │
└─────────────────────────────────────────────────┴──────────┴──────────┴──────────────┘
/*** 雪花算法ID生成器*/
@Component
public class SnowflakeIdGenerator {// 起始时间戳 (2020-01-01)private final long START_TIMESTAMP = 1577836800000L;// 各部分位数private final long SEQUENCE_BITS = 12;private final long MACHINE_BITS = 10;private final long TIMESTAMP_BITS = 41;// 最大值private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);private final long MAX_MACHINE_ID = ~(-1L << MACHINE_BITS);// 位移private final long MACHINE_SHIFT = SEQUENCE_BITS;private final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_BITS;private long machineId;private long sequence = 0L;private long lastTimestamp = -1L;public SnowflakeIdGenerator() {// 从配置或环境变量获取机器IDthis.machineId = getMachineId();}public synchronized long nextId() {long currentTimestamp = System.currentTimeMillis();// 时钟回拨检查if (currentTimestamp < lastTimestamp) {throw new RuntimeException("时钟回拨,拒绝生成ID");}if (currentTimestamp == lastTimestamp) {// 同一毫秒内,序列号递增sequence = (sequence + 1) & MAX_SEQUENCE;if (sequence == 0) {// 序列号溢出,等待下一毫秒currentTimestamp = waitNextMillis(currentTimestamp);}} else {// 不同毫秒,序列号重置sequence = 0L;}lastTimestamp = currentTimestamp;// 组装IDreturn ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)| (machineId << MACHINE_SHIFT)| sequence;}private long waitNextMillis(long currentTimestamp) {while (currentTimestamp <= lastTimestamp) {currentTimestamp = System.currentTimeMillis();}return currentTimestamp;}private long getMachineId() {// 可以从配置文件、环境变量或数据库获取String machineIdStr = System.getProperty("machine.id", "1");long id = Long.parseLong(machineIdStr);if (id > MAX_MACHINE_ID || id < 0) {throw new IllegalArgumentException("机器ID超出范围");}return id;}
}
3.2 数据库表设计中的ID策略
-- 用户表 - 使用雪花算法ID
CREATE TABLE users (id BIGINT UNSIGNED PRIMARY KEY COMMENT '雪花算法生成的用户ID',username VARCHAR(50) UNIQUE NOT NULL,email VARCHAR(100) UNIQUE,phone VARCHAR(20) UNIQUE,password_hash VARCHAR(255) NOT NULL,salt VARCHAR(32) NOT NULL,status TINYINT DEFAULT 1 COMMENT '1:正常 0:禁用',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,INDEX idx_username (username),INDEX idx_email (email),INDEX idx_phone (phone),INDEX idx_status (status),INDEX idx_created_at (created_at)
);-- 订单表 - 雪花算法ID + 业务订单号
CREATE TABLE orders (id BIGINT UNSIGNED PRIMARY KEY COMMENT '雪花算法ID',order_no VARCHAR(32) UNIQUE NOT NULL COMMENT '业务订单号',user_id BIGINT UNSIGNED NOT NULL,total_amount DECIMAL(12,2) NOT NULL,status TINYINT DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,INDEX idx_order_no (order_no),INDEX idx_user_id (user_id),INDEX idx_status_created (status, created_at)
);-- 订单号生成规则
-- 格式:日期(8位) + 机器ID(2位) + 序列号(6位)
-- 示例:2024010101000001
3.3 分布式ID生成服务
/*** 分布式ID生成服务*/
@Service
public class DistributedIdService {@Autowiredprivate SnowflakeIdGenerator snowflakeGenerator;@Autowiredprivate RedisTemplate<String, String> redisTemplate;/*** 生成用户ID*/public Long generateUserId() {return snowflakeGenerator.nextId();}/*** 生成订单号*/public String generateOrderNo() {String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));String machineId = String.format("%02d", getMachineId());// 使用Redis生成序列号,保证单机唯一String key = "order_seq:" + date + ":" + machineId;Long seq = redisTemplate.opsForValue().increment(key);// 设置过期时间为2天redisTemplate.expire(key, Duration.ofDays(2));return date + machineId + String.format("%06d", seq);}/*** 生成商品SKU编码*/public String generateSkuCode(Long categoryId) {String categoryCode = String.format("%04d", categoryId);String timestamp = String.valueOf(System.currentTimeMillis() % 100000);String random = String.format("%03d", new Random().nextInt(1000));return "SKU" + categoryCode + timestamp + random;}private int getMachineId() {// 从配置获取机器IDreturn Integer.parseInt(System.getProperty("machine.id", "1"));}
}
📊 第四章:索引设计与优化
4.1 索引类型与选择
架构师老王: “索引就像书的目录,选对了事半功倍,选错了适得其反。”
-- 单列索引
CREATE TABLE products (id BIGINT PRIMARY KEY,name VARCHAR(200),category_id BIGINT,brand_id BIGINT,price DECIMAL(10,2),stock INT,status TINYINT,created_at TIMESTAMP,-- 普通索引INDEX idx_category (category_id),INDEX idx_brand (brand_id),INDEX idx_price (price),INDEX idx_status (status),-- 唯一索引UNIQUE INDEX uk_name_brand (name, brand_id),-- 前缀索引(适用于长字符串)INDEX idx_name_prefix (name(20))
);-- 复合索引设计
CREATE TABLE user_orders (id BIGINT PRIMARY KEY,user_id BIGINT,status TINYINT,order_type TINYINT,total_amount DECIMAL(12,2),created_at TIMESTAMP,-- 复合索引:最左前缀原则INDEX idx_user_status_created (user_id, status, created_at),INDEX idx_status_type_amount (status, order_type, total_amount),-- 覆盖索引:包含查询所需的所有字段INDEX idx_user_cover (user_id, status, total_amount, created_at)
);-- 函数索引(MySQL 8.0+)
CREATE TABLE users (id BIGINT PRIMARY KEY,email VARCHAR(100),phone VARCHAR(20),created_at TIMESTAMP,-- 函数索引:支持大小写不敏感查询INDEX idx_email_lower ((LOWER(email))),-- 表达式索引INDEX idx_created_year ((YEAR(created_at)))
);
4.2 索引优化策略
-- 查询优化示例
-- ❌ 低效查询
SELECT * FROM orders
WHERE DATE(created_at) = '2024-01-01';-- ✅ 高效查询(使用范围查询,能利用索引)
SELECT * FROM orders
WHERE created_at >= '2024-01-01 00:00:00' AND created_at < '2024-01-02 00:00:00';-- ❌ 低效查询(函数导致索引失效)
SELECT * FROM users WHERE UPPER(username) = 'ADMIN';-- ✅ 高效查询(使用函数索引或存储计算结果)
SELECT * FROM users WHERE username = 'admin';-- 分页查询优化
-- ❌ 深分页性能差
SELECT * FROM products
ORDER BY created_at DESC
LIMIT 100000, 20;-- ✅ 使用游标分页
SELECT * FROM products
WHERE created_at < '2024-01-01 10:00:00'
ORDER BY created_at DESC
LIMIT 20;-- ✅ 使用ID分页
SELECT * FROM products
WHERE id > 1000000
ORDER BY id
LIMIT 20;
4.3 索引监控与维护
-- 查看索引使用情况
SELECT TABLE_SCHEMA,TABLE_NAME,INDEX_NAME,CARDINALITY,SUB_PART,NULLABLE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'your_database'
ORDER BY TABLE_NAME, SEQ_IN_INDEX;-- 查看未使用的索引
SELECT s.TABLE_SCHEMA,s.TABLE_NAME,s.INDEX_NAME
FROM information_schema.STATISTICS s
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage tON s.TABLE_SCHEMA = t.OBJECT_SCHEMAAND s.TABLE_NAME = t.OBJECT_NAMEAND s.INDEX_NAME = t.INDEX_NAME
WHERE t.INDEX_NAME IS NULLAND s.TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema')AND s.INDEX_NAME != 'PRIMARY';-- 分析表和索引
ANALYZE TABLE products;-- 优化表(重建索引)
OPTIMIZE TABLE products;
🔄 第五章:分库分表设计
5.1 垂直拆分
架构师老王: “当单表数据量过大时,我们需要考虑分库分表。先看垂直拆分。”
-- 原始用户表(字段过多)
CREATE TABLE users_original (id BIGINT PRIMARY KEY,username VARCHAR(50),email VARCHAR(100),phone VARCHAR(20),password_hash VARCHAR(255),salt VARCHAR(32),nickname VARCHAR(50),avatar_url VARCHAR(500),gender TINYINT,birthday DATE,province VARCHAR(50),city VARCHAR(50),district VARCHAR(50),address VARCHAR(200),bio TEXT,hobby TEXT,education VARCHAR(100),occupation VARCHAR(100),company VARCHAR(100),created_at TIMESTAMP,updated_at TIMESTAMP
);-- 垂直拆分后
-- 用户基础信息表
CREATE TABLE users (id BIGINT PRIMARY KEY,username VARCHAR(50) UNIQUE NOT NULL,email VARCHAR(100) UNIQUE,phone VARCHAR(20) UNIQUE,password_hash VARCHAR(255) NOT NULL,salt VARCHAR(32) NOT NULL,status TINYINT DEFAULT 1,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,INDEX idx_username (username),INDEX idx_email (email),INDEX idx_phone (phone)
);-- 用户详细信息表
CREATE TABLE user_profiles (user_id BIGINT PRIMARY KEY,nickname VARCHAR(50),avatar_url VARCHAR(500),gender TINYINT,birthday DATE,bio TEXT,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,FOREIGN KEY (user_id) REFERENCES users(id)
);-- 用户地址信息表
CREATE TABLE user_addresses (id BIGINT PRIMARY KEY,user_id BIGINT NOT NULL,province VARCHAR(50),city VARCHAR(50),district VARCHAR(50),detail_address VARCHAR(200),is_default TINYINT DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,INDEX idx_user_id (user_id),FOREIGN KEY (user_id) REFERENCES users(id)
);
5.2 水平分表
-- 订单表水平分表(按月分表)
-- 2024年1月订单表
CREATE TABLE orders_202401 (id BIGINT PRIMARY KEY,order_no VARCHAR(32) UNIQUE NOT NULL,user_id BIGINT NOT NULL,total_amount DECIMAL(12,2),status TINYINT DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,INDEX idx_order_no (order_no),INDEX idx_user_id (user_id),INDEX idx_status (status),INDEX idx_created_at (created_at)
);-- 2024年2月订单表
CREATE TABLE orders_202402 (-- 结构相同id BIGINT PRIMARY KEY,order_no VARCHAR(32) UNIQUE NOT NULL,user_id BIGINT NOT NULL,total_amount DECIMAL(12,2),status TINYINT DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,INDEX idx_order_no (order_no),INDEX idx_user_id (user_id),INDEX idx_status (status),INDEX idx_created_at (created_at)
);-- 分表路由逻辑
/*** 分表路由服务*/
@Service
public class ShardingService {/*** 根据时间路由到对应的订单表*/public String getOrderTableName(LocalDateTime createTime) {String suffix = createTime.format(DateTimeFormatter.ofPattern("yyyyMM"));return "orders_" + suffix;}/*** 根据用户ID路由到对应的用户表*/public String getUserTableName(Long userId) {// 按用户ID取模分表int tableIndex = (int) (userId % 16);return "users_" + String.format("%02d", tableIndex);}/*** 获取查询时间范围内的所有表名*/public List<String> getOrderTableNames(LocalDateTime startTime, LocalDateTime endTime) {List<String> tableNames = new ArrayList<>();LocalDateTime current = startTime.withDayOfMonth(1);while (!current.isAfter(endTime)) {String suffix = current.format(DateTimeFormatter.ofPattern("yyyyMM"));tableNames.add("orders_" + suffix);current = current.plusMonths(1);}return tableNames;}
}
5.3 分库策略
# ShardingSphere配置示例
spring:shardingsphere:datasource:names: ds0,ds1,ds2,ds3ds0:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/ecommerce_0username: rootpassword: passwordds1:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/ecommerce_1username: rootpassword: passwordds2:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/ecommerce_2username: rootpassword: passwordds3:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/ecommerce_3username: rootpassword: passwordrules:sharding:tables:users:actual-data-nodes: ds$->{0..3}.users_$->{00..15}database-strategy:standard:sharding-column: idsharding-algorithm-name: user-database-inlinetable-strategy:standard:sharding-column: idsharding-algorithm-name: user-table-inlineorders:actual-data-nodes: ds$->{0..3}.orders_$->{202401..202412}database-strategy:standard:sharding-column: user_idsharding-algorithm-name: order-database-inlinetable-strategy:standard:sharding-column: created_atsharding-algorithm-name: order-table-inlinesharding-algorithms:user-database-inline:type: INLINEprops:algorithm-expression: ds$->{id % 4}user-table-inline:type: INLINEprops:algorithm-expression: users_$->{String.format('%02d', id % 16)}order-database-inline:type: INLINEprops:algorithm-expression: ds$->{user_id % 4}order-table-inline:type: INLINEprops:algorithm-expression: orders_$->{created_at.format('yyyyMM')}
🎯 第六章:性能优化实战
6.1 查询优化
-- 商品搜索优化
CREATE TABLE products (id BIGINT PRIMARY KEY,name VARCHAR(200) NOT NULL,category_id BIGINT,brand_id BIGINT,price DECIMAL(10,2),stock INT DEFAULT 0,sales_count INT DEFAULT 0,rating DECIMAL(3,2) DEFAULT 0,status TINYINT DEFAULT 1,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,-- 复合索引优化多条件查询INDEX idx_category_price_sales (category_id, price, sales_count DESC),INDEX idx_brand_price (brand_id, price),INDEX idx_status_created (status, created_at DESC),-- 全文索引支持商品名称搜索FULLTEXT INDEX ft_name (name)
);-- 优化后的查询
-- 分类 + 价格区间 + 排序
SELECT id, name, price, sales_count
FROM products
WHERE category_id = 1001 AND price BETWEEN 100 AND 500 AND status = 1
ORDER BY sales_count DESC
LIMIT 20;-- 使用全文索引搜索
SELECT id, name, price
FROM products
WHERE MATCH(name) AGAINST('手机 华为' IN NATURAL LANGUAGE MODE)AND status = 1
ORDER BY rating DESC
LIMIT 20;
6.2 统计查询优化
-- 订单统计表(预计算)
CREATE TABLE order_statistics (id BIGINT PRIMARY KEY,stat_date DATE NOT NULL,stat_type TINYINT NOT NULL COMMENT '1:日统计 2:月统计',total_orders INT DEFAULT 0,total_amount DECIMAL(15,2) DEFAULT 0,avg_amount DECIMAL(10,2) DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,UNIQUE INDEX uk_date_type (stat_date, stat_type),INDEX idx_stat_date (stat_date)
);-- 用户行为统计表
CREATE TABLE user_behavior_stats (user_id BIGINT PRIMARY KEY,total_orders INT DEFAULT 0,total_amount DECIMAL(15,2) DEFAULT 0,last_order_time TIMESTAMP NULL,avg_order_amount DECIMAL(10,2) DEFAULT 0,favorite_category_id BIGINT,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,INDEX idx_total_amount (total_amount DESC),INDEX idx_last_order (last_order_time DESC)
);-- 定时任务更新统计数据
/*** 统计数据更新服务*/
@Service
public class StatisticsService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate StatisticsMapper statisticsMapper;/*** 每日统计任务*/@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行public void updateDailyStatistics() {LocalDate yesterday = LocalDate.now().minusDays(1);// 计算昨日订单统计OrderStatistics stats = orderMapper.getDailyStatistics(yesterday);// 更新或插入统计数据statisticsMapper.upsertDailyStats(stats);log.info("更新日统计数据完成: {}", yesterday);}/*** 用户行为统计更新*/@Asyncpublic void updateUserBehaviorStats(Long userId) {UserBehaviorStats stats = orderMapper.getUserBehaviorStats(userId);statisticsMapper.updateUserBehaviorStats(stats);}
}
📋 面试常考知识点
Q1: 如何选择合适的数据类型?
A:
- 数值类型:根据取值范围选择最小的类型
- 字符串:固定长度用CHAR,变长用VARCHAR
- 时间:TIMESTAMP vs DATETIME的区别
- 金额:使用DECIMAL避免精度问题
Q2: 雪花算法的优缺点?
A:
- 优点:全局唯一、趋势递增、高性能
- 缺点:依赖系统时钟、机器ID管理复杂
- 替代方案:UUID、数据库自增ID、Redis生成
Q3: 如何设计高效的索引?
A:
- 遵循最左前缀原则
- 避免在索引列上使用函数
- 考虑覆盖索引减少回表
- 定期监控和清理无用索引
Q4: 什么时候需要分库分表?
A:
- 单表数据量超过1000万
- 单库连接数不够用
- 读写QPS达到瓶颈
- 需要考虑数据一致性和跨库查询问题
🎯 最佳实践总结
架构师老王: “数据库设计的核心原则:”
- 合理范式化: 在性能和规范之间找平衡
- 选择合适类型: 够用就好,不要过度设计
- 索引设计: 查询驱动,定期优化
- 分库分表: 提前规划,平滑扩展
- 监控运维: 持续优化,预防问题
小明: “原来数据库设计有这么多门道!”
架构师老王: “是的,好的数据库设计是系统性能的基石。记住:设计时多思考,运行时少烦恼!”