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達到瓶頸
- 需要考慮數據一致性和跨庫查詢問題
🎯 最佳實踐總結
架構師老王: “數據庫設計的核心原則:”
- 合理范式化: 在性能和規范之間找平衡
- 選擇合適類型: 夠用就好,不要過度設計
- 索引設計: 查詢驅動,定期優化
- 分庫分表: 提前規劃,平滑擴展
- 監控運維: 持續優化,預防問題
小明: “原來數據庫設計有這么多門道!”
架構師老王: “是的,好的數據庫設計是系統性能的基石。記住:設計時多思考,運行時少煩惱!”