引言
数据库是现代应用系统的核心组件,其设计质量直接影响系统的性能、可扩展性和可维护性。随着数据量的增长和业务复杂度的提升,数据库优化变得越来越重要。本文将详细介绍数据库设计优化的核心策略和最佳实践。
1. 关系型数据库设计
1.1 规范化设计
数据库规范化是设计高质量关系型数据库的基础:
-- 第一范式:原子性
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100),
phone VARCHAR(20)
);
-- 第二范式:消除部分依赖
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
order_date DATE,
total_amount DECIMAL(10,2),
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT,
product_id INT,
quantity INT,
unit_price DECIMAL(10,2),
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
-- 第三范式:消除传递依赖
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
category_id INT,
price DECIMAL(10,2),
FOREIGN KEY (category_id) REFERENCES categories(id)
);
CREATE TABLE categories (
id INT PRIMARY KEY,
name VARCHAR(50),
description TEXT
);
1.2 反规范化策略
在某些场景下,适度的反规范化可以提升查询性能:
-- 冗余字段优化查询
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
user_name VARCHAR(100), -- 冗余字段
user_email VARCHAR(100), -- 冗余字段
order_date DATE,
total_amount DECIMAL(10,2),
status VARCHAR(20),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 预计算字段
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2),
discount_rate DECIMAL(3,2),
final_price DECIMAL(10,2), -- 预计算字段
stock_quantity INT,
sold_quantity INT,
total_revenue DECIMAL(12,2) -- 预计算字段
);
2. 索引优化策略
2.1 索引类型选择
-- 主键索引(自动创建)
CREATE TABLE users (
id INT PRIMARY KEY, -- 自动创建主键索引
username VARCHAR(50) UNIQUE, -- 唯一索引
email VARCHAR(100)
);
-- 普通索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);
-- 复合索引
CREATE INDEX idx_orders_user_date ON orders(user_id, order_date);
CREATE INDEX idx_products_category_price ON products(category_id, price);
-- 部分索引
CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';
-- 函数索引
CREATE INDEX idx_users_lower_email ON users(LOWER(email));
2.2 索引优化技巧
-- 覆盖索引
CREATE INDEX idx_orders_covering ON orders(user_id, order_date, total_amount, status);
-- 查询可以使用覆盖索引,避免回表
SELECT user_id, order_date, total_amount
FROM orders
WHERE user_id = 123 AND order_date >= '2024-01-01';
-- 前缀索引(适用于长字符串)
CREATE INDEX idx_products_name_prefix ON products(name(20));
-- 倒序索引
CREATE INDEX idx_orders_date_desc ON orders(order_date DESC);
3. 查询优化
3.1 SQL优化技巧
-- 避免SELECT *
SELECT id, name, email FROM users WHERE status = 'active';
-- 使用EXISTS代替IN(大数据量时)
SELECT * FROM orders o
WHERE EXISTS (
SELECT 1 FROM users u
WHERE u.id = o.user_id AND u.status = 'active'
);
-- 使用JOIN代替子查询
SELECT o.*, u.name, u.email
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'active';
-- 分页查询优化
SELECT * FROM orders
WHERE id > (SELECT id FROM orders ORDER BY id LIMIT 10000, 1)
ORDER BY id
LIMIT 20;
3.2 查询计划分析
-- 查看执行计划
EXPLAIN SELECT o.*, u.name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.order_date >= '2024-01-01';
-- 强制使用索引
SELECT * FROM orders FORCE INDEX (idx_orders_user_date)
WHERE user_id = 123 AND order_date >= '2024-01-01';
-- 分析表统计信息
ANALYZE TABLE orders;
4. 分库分表策略
4.1 水平分表
-- 按时间分表
CREATE TABLE orders_202401 (
id INT PRIMARY KEY,
user_id INT,
order_date DATE,
total_amount DECIMAL(10,2)
);
CREATE TABLE orders_202402 (
id INT PRIMARY KEY,
user_id INT,
order_date DATE,
total_amount DECIMAL(10,2)
);
-- 按用户ID哈希分表
CREATE TABLE orders_0 (
id INT PRIMARY KEY,
user_id INT,
order_date DATE,
total_amount DECIMAL(10,2)
);
CREATE TABLE orders_1 (
id INT PRIMARY KEY,
user_id INT,
order_date DATE,
total_amount DECIMAL(10,2)
);
4.2 垂直分表
-- 主表
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100),
status VARCHAR(20),
created_at TIMESTAMP
);
-- 扩展表
CREATE TABLE user_profiles (
user_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
phone VARCHAR(20),
address TEXT,
avatar_url VARCHAR(200),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 统计表
CREATE TABLE user_stats (
user_id INT PRIMARY KEY,
login_count INT DEFAULT 0,
last_login_at TIMESTAMP,
total_orders INT DEFAULT 0,
total_spent DECIMAL(12,2) DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(id)
);
5. 缓存策略
5.1 应用层缓存
@Service
public class UserService {
@Autowired
private RedisTemplate<String, User> redisTemplate;
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
return userRepository.findById(id);
}
@CacheEvict(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
@Cacheable(value = "user_orders", key = "#userId")
public List<Order> getUserOrders(Long userId) {
return orderRepository.findByUserId(userId);
}
}
5.2 数据库缓存
-- 查询缓存
SET SESSION query_cache_type = ON;
SET SESSION query_cache_size = 67108864; -- 64MB
-- 表缓存
CREATE TABLE user_cache (
user_id INT PRIMARY KEY,
user_data JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 物化视图
CREATE MATERIALIZED VIEW user_order_summary AS
SELECT
u.id as user_id,
u.username,
COUNT(o.id) as order_count,
SUM(o.total_amount) as total_spent,
MAX(o.order_date) as last_order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username;
6. 读写分离
6.1 主从复制配置
-- 主库配置
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog_format = ROW
sync_binlog = 1
-- 从库配置
[mysqld]
server-id = 2
relay-log = mysql-relay-bin
read_only = 1
6.2 应用层读写分离
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource routingDataSource() {
RoutingDataSource routingDataSource = new RoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(masterDataSource());
return routingDataSource;
}
}
7. 数据库连接池优化
7.1 连接池配置
# application.yml
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 60000
connection-test-query: SELECT 1
7.2 连接池监控
@Component
public class DataSourceMonitor {
@Autowired
private DataSource dataSource;
@Scheduled(fixedRate = 60000)
public void monitorConnectionPool() {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
HikariPoolMXBean poolMXBean = hikariDataSource.getHikariPoolMXBean();
log.info("Active connections: {}", poolMXBean.getActiveConnections());
log.info("Idle connections: {}", poolMXBean.getIdleConnections());
log.info("Total connections: {}", poolMXBean.getTotalConnections());
}
}
}
8. 数据库监控
8.1 性能监控
-- 慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- 查看慢查询
SELECT
sql_text,
exec_count,
avg_timer_wait/1000000000 as avg_time_sec,
sum_timer_wait/1000000000 as total_time_sec
FROM performance_schema.events_statements_summary_by_digest
WHERE avg_timer_wait > 1000000000
ORDER BY avg_timer_wait DESC;
-- 查看表统计信息
SELECT
table_name,
table_rows,
data_length,
index_length,
(data_length + index_length) as total_size
FROM information_schema.tables
WHERE table_schema = 'your_database'
ORDER BY total_size DESC;
8.2 锁监控
-- 查看当前锁
SELECT
r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
b.trx_query blocking_query
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
9. 备份与恢复
9.1 备份策略
#!/bin/bash
# 全量备份脚本
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backup/mysql"
DB_NAME="your_database"
# 创建备份目录
mkdir -p $BACKUP_DIR
# 全量备份
mysqldump --single-transaction --routines --triggers \
--master-data=2 --databases $DB_NAME \
> $BACKUP_DIR/full_backup_$DATE.sql
# 压缩备份文件
gzip $BACKUP_DIR/full_backup_$DATE.sql
# 删除7天前的备份
find $BACKUP_DIR -name "full_backup_*.sql.gz" -mtime +7 -delete
9.2 增量备份
#!/bin/bash
# 增量备份脚本
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backup/mysql"
BINLOG_DIR="/var/lib/mysql"
# 刷新日志
mysql -e "FLUSH LOGS;"
# 复制binlog文件
cp $BINLOG_DIR/mysql-bin.* $BACKUP_DIR/
# 压缩binlog文件
gzip $BACKUP_DIR/mysql-bin.*
10. 总结
数据库优化是一个持续的过程,需要从多个维度进行考虑:
- 设计层面:规范化设计、反规范化策略
- 索引层面:合理创建索引、覆盖索引、复合索引
- 查询层面:SQL优化、查询计划分析
- 架构层面:分库分表、读写分离、缓存策略
- 运维层面:连接池优化、监控告警、备份恢复
金牧科技在数据库设计和优化方面拥有丰富的实践经验,如果您需要数据库架构咨询或优化服务,欢迎联系我们。
相关阅读: