{T}

数据库调优

调优的目标与意义

数据库调优的核心目标是让数据库运行更快、更稳定,具体表现为:

指标说明
响应时间单次请求的处理时间更短
吞吐量单位时间内处理的请求数量更多
资源利用率CPU、内存、I/O使用更合理
稳定性高并发下系统保持稳定运行

随着用户量增加和应用复杂度提升,调优目标需要更精细的定位。不同场景下的瓶颈各不相同:

  • 促销活动:大规模并发访问带来的压力
  • 复杂业务:多表关联、复杂查询的性能问题
  • 数据增长:数据量增大后的查询效率下降

数据库问题诊断方法

1. 用户反馈分析

用户是服务的直接对象,他们的反馈往往能第一时间发现问题:

反馈类型可能的问题
页面加载慢查询效率低、索引缺失
操作超时锁等待、事务阻塞
数据不一致并发控制问题
服务不可用资源耗尽、连接池满

2. 日志分析

通过分析各类日志定位问题:

bash
# MySQL错误日志
/var/log/mysql/error.log

# 慢查询日志
/var/log/mysql/slow.log

# 系统日志
/var/log/syslog

慢查询日志配置

sql
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
-- 设置慢查询阈值(秒)
SET GLOBAL long_query_time = 2;
-- 记录未使用索引的查询
SET GLOBAL log_queries_not_using_indexes = 'ON';

3. 服务器资源监控

监控关键资源指标:

资源类型监控指标异常表现
CPU使用率、负载持续高使用率
内存使用量、缓存命中率内存不足、频繁换页
磁盘I/O读写速率、IOPSI/O等待时间长
网络带宽、连接数网络延迟高

常用监控命令

bash
# CPU使用情况
top -p $(pidof mysqld)

# 内存使用情况
free -m

# 磁盘I/O
iostat -x 1

# 网络连接
netstat -anp | grep mysql

4. 数据库内部监控

活动会话监控

sql
-- 查看当前活动会话
SHOW PROCESSLIST;

-- 查看完整进程列表
SHOW FULL PROCESSLIST;

-- 查看锁等待情况
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

关键监控指标

指标说明查询方式
活动连接数当前活跃的数据库连接SHOW STATUS LIKE 'Threads_connected'
慢查询数执行时间超过阈值的查询数SHOW STATUS LIKE 'Slow_queries'
缓存命中率查询缓存命中比例SHOW STATUS LIKE 'Qcache%'
锁等待数等待锁的事务数量SHOW STATUS LIKE 'Innodb_row_lock%'

数据库调优维度

维度一:选择适合的DBMS

关系型数据库(RDBMS)

数据库特点适用场景
Oracle功能强大、安全性高金融、电信等关键业务
SQL Server与Windows集成好企业内部应用
MySQL开源、生态丰富互联网应用、中小型项目
PostgreSQL功能完善、扩展性强复杂查询、地理信息

MySQL存储引擎选择

sql
-- 查看支持的存储引擎
SHOW ENGINES;

-- InnoDB:支持事务、行级锁、外键(推荐)
-- MyISAM:不支持事务、表级锁、读性能好

NoSQL数据库

类型代表产品特点适用场景
键值型Redis、Memcached高性能读写缓存、会话存储
文档型MongoDB灵活的数据结构内容管理、日志
列式存储HBase、Cassandra高压缩比、聚合快大数据分析
图数据库Neo4j关系遍历高效社交网络、推荐
搜索引擎Elasticsearch全文检索强搜索、日志分析

维度二:优化表设计

表设计原则

code
1. 基础原则:遵循第三范式(3NF)
2. 查询优化:适度反范式化
3. 字段选择:合适的数据类型
4. 存储引擎:根据需求选择

字段类型优化

sql
-- 数值类型选择
-- 好的选择:使用数值类型
status TINYINT          -- 状态码
price DECIMAL(10,2)     -- 价格

-- 不好的选择:使用字符类型
status VARCHAR(10)      -- 存储'active'、'inactive'
price VARCHAR(20)       -- 字符串存储数值

-- 字符类型选择
-- 固定长度:CHAR
phone CHAR(11)          -- 手机号
id_card CHAR(18)        -- 身份证号

-- 可变长度:VARCHAR
name VARCHAR(50)        -- 姓名
address VARCHAR(200)    -- 地址

字段长度优化

sql
-- 根据实际需求设置合理长度
-- 好的选择
username VARCHAR(32)    -- 用户名通常不超过32字符
email VARCHAR(100)      -- 邮箱通常不超过100字符

-- 不好的选择
username VARCHAR(255)   -- 过长浪费存储
email TEXT              -- 大字段影响性能

维度三:优化逻辑查询

逻辑查询优化通过改变SQL语句内容,采用等价变换提升执行效率。

查询重写技巧

1. 避免在WHERE子句中使用函数

sql
-- 低效写法(索引失效)
SELECT comment_id, comment_text, comment_time 
FROM product_comment 
WHERE SUBSTRING(comment_text, 1, 3) = 'abc';

-- 高效写法(使用索引)
SELECT comment_id, comment_text, comment_time 
FROM product_comment 
WHERE comment_text LIKE 'abc%';

2. 子查询优化

sql
-- 低效写法:相关子查询
SELECT * FROM orders o
WHERE EXISTS (SELECT 1 FROM customer c WHERE c.id = o.customer_id AND c.level = 'VIP');

-- 高效写法:JOIN查询
SELECT o.* 
FROM orders o
INNER JOIN customer c ON o.customer_id = c.id
WHERE c.level = 'VIP';

3. 使用EXISTS替代IN

sql
-- 当子查询结果集较大时
-- 低效写法
SELECT * FROM orders WHERE customer_id IN (SELECT id FROM customer WHERE level = 'VIP');

-- 高效写法
SELECT * FROM orders o WHERE EXISTS (SELECT 1 FROM customer c WHERE c.id = o.customer_id AND c.level = 'VIP');

**4. 避免SELECT ***

sql
-- 低效写法
SELECT * FROM product_comment WHERE product_id = 10001;

-- 高效写法:只查询需要的字段
SELECT comment_id, comment_text, comment_time 
FROM product_comment 
WHERE product_id = 10001;

常用查询优化规则

优化规则说明
条件简化移除冗余条件,合并相似条件
视图重写将视图展开为基表查询
连接消除移除不必要的表连接
谓词下推将过滤条件尽可能下推到数据源

维度四:优化物理查询

物理查询优化通过建立合适的索引和选择最优的访问路径来提升性能。

索引创建原则

1. 选择合适的字段创建索引

sql
-- 适合创建索引的字段
-- WHERE条件中频繁使用的字段
CREATE INDEX idx_product_id ON product_comment(product_id);

-- JOIN关联字段
CREATE INDEX idx_customer_id ON orders(customer_id);

-- ORDER BY排序字段
CREATE INDEX idx_create_time ON orders(create_time);

-- 不适合创建索引的字段
-- 性别字段(区分度低)
-- 状态字段(取值少)
-- 频繁更新的字段

2. 联合索引设计

sql
-- 遵循最左前缀原则
-- 联合索引 (a, b, c)
CREATE INDEX idx_abc ON orders(a, b, c);

-- 可以使用索引的查询
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3

-- 不能使用索引的查询
WHERE b = 2
WHERE c = 3
WHERE b = 2 AND c = 3

3. 索引使用注意事项

sql
-- 避免索引失效的情况

-- 1. 避免对索引字段进行运算
-- 索引失效
WHERE YEAR(create_time) = 2023
-- 索引有效
WHERE create_time >= '2023-01-01' AND create_time < '2024-01-01'

-- 2. 避免隐式类型转换
-- 索引失效(phone是VARCHAR类型)
WHERE phone = 13800138000
-- 索引有效
WHERE phone = '13800138000'

-- 3. 避免使用NOT IN、<>、!=
-- 索引可能失效
WHERE status != 1
-- 改写为
WHERE status IN (0, 2, 3)

访问路径选择

访问方式说明适用场景
全表扫描扫描整张表小表、无合适索引
索引扫描通过索引定位数据有合适索引
索引覆盖只需访问索引查询字段都在索引中

多表连接优化

sql
-- 连接方式选择

-- 1. 嵌套循环连接(Nested Loop)
-- 适合:小表驱动大表
SELECT * FROM small_table s JOIN large_table l ON s.id = l.sid;

-- 2. 哈希连接(Hash Join)
-- 适合:大表等值连接
SELECT * FROM table_a a JOIN table_b b ON a.id = b.aid;

-- 3. 排序合并连接(Sort Merge)
-- 适合:已排序的数据
SELECT * FROM sorted_table_a a JOIN sorted_table_b b ON a.id = b.id;

维度五:使用缓存

Redis vs Memcached

特性RedisMemcached
数据类型丰富(String、List、Set等)简单(Key-Value)
持久化支持(RDB、AOF)不支持
集群原生支持需要客户端实现
线程模型单线程多线程
适用场景复杂数据结构、持久化需求简单缓存

缓存使用策略

code
缓存使用流程:
1. 查询请求先访问缓存
2. 缓存命中:直接返回
3. 缓存未命中:查询数据库,结果写入缓存

缓存穿透解决方案

python
# 方案1:缓存空值
def get_data(key):
    data = cache.get(key)
    if data is not None:
        return data if data != 'NULL' else None
    
    data = db.query(key)
    if data is None:
        cache.set(key, 'NULL', timeout=60)  # 缓存空值
    else:
        cache.set(key, data, timeout=3600)
    return data

# 方案2:布隆过滤器
def get_data_with_bloom(key):
    if not bloom_filter.might_contain(key):
        return None  # 确定不存在
    # 继续正常查询流程

维度六:库级优化

读写分离

code
架构示意:
┌─────────┐     写请求     ┌─────────┐
│         │ ──────────────>│  Master │
│ 应用服务 │               └─────────┘
│         │                     │
└─────────┘               主从同步
     │                         │
     │ 读请求              ┌────┴────┐
     └────────────────────>│  Slave  │
                           └─────────┘

读写分离配置示例

java
// Spring配置读写分离
@Configuration
public class DataSourceConfig {
    
    @Bean
    @Primary
    public DataSource masterDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://master:3306/db")
            .build();
    }
    
    @Bean
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://slave:3306/db")
            .build();
    }
}

分库分表

垂直切分:按业务模块分库

code
单库结构:
┌─────────────────────────┐
│      单一数据库          │
│  ┌─────┐ ┌─────┐ ┌─────┐│
│  │用户表│ │订单表│ │商品表││
│  └─────┘ └─────┘ └─────┘│
└─────────────────────────┘

垂直分库后:
┌─────────┐  ┌─────────┐  ┌─────────┐
│ 用户库   │  │ 订单库   │  │ 商品库   │
│┌───────┐│  │┌───────┐│  │┌───────┐│
││用户表  ││  ││订单表  ││  ││商品表  ││
│└───────┘│  │└───────┘│  │└───────┘│
└─────────┘  └─────────┘  └─────────┘

水平切分:按数据规则分表

sql
-- 按ID范围分表
orders_0: id 1-1000000
orders_1: id 1000001-2000000
orders_2: id 2000001-3000000

-- 按Hash分表
orders_0: id % 4 = 0
orders_1: id % 4 = 1
orders_2: id % 4 = 2
orders_3: id % 4 = 3

分库分表中间件

中间件特点
ShardingSphereApache开源,功能全面
MyCat国产开源,社区活跃
VitessYouTube开源,适合大规模

调优实践案例

案例1:慢查询优化

问题:某查询执行时间超过5秒

sql
-- 原始查询
SELECT * FROM orders 
WHERE DATE_FORMAT(create_time, '%Y-%m') = '2023-06';

分析:使用EXPLAIN查看执行计划

sql
EXPLAIN SELECT * FROM orders 
WHERE DATE_FORMAT(create_time, '%Y-%m') = '2023-06';

-- 结果:type=ALL,全表扫描,索引失效

优化方案

sql
-- 优化后的查询
SELECT * FROM orders 
WHERE create_time >= '2023-06-01' AND create_time < '2023-07-01';

-- 添加索引
CREATE INDEX idx_create_time ON orders(create_time);

效果:执行时间从5秒降低到0.05秒

案例2:大表优化

问题:订单表数据量超过1亿,查询缓慢

优化方案

  1. 历史数据归档
sql
-- 创建历史表
CREATE TABLE orders_history LIKE orders;

-- 迁移历史数据
INSERT INTO orders_history 
SELECT * FROM orders WHERE create_time < '2022-01-01';

-- 删除已迁移数据
DELETE FROM orders WHERE create_time < '2022-01-01';
  1. 分区表
sql
-- 按月分区
ALTER TABLE orders PARTITION BY RANGE (YEAR(create_time)*100 + MONTH(create_time)) (
    PARTITION p202301 VALUES LESS THAN (202302),
    PARTITION p202302 VALUES LESS THAN (202303),
    PARTITION p202303 VALUES LESS THAN (202304),
    PARTITION pmax VALUES LESS THAN MAXVALUE
);

调优检查清单

检查项检查内容
SQL层面是否使用索引、是否避免全表扫描
表设计字段类型是否合理、是否过度范式化
索引设计索引是否覆盖常用查询、是否存在冗余索引
缓存策略热点数据是否缓存、缓存命中率
架构设计是否需要读写分离、是否需要分库分表
资源监控CPU、内存、I/O是否正常

总结

数据库调优是一个系统工程,需要从多个维度综合考虑:

code
调优优先级:
1. SQL优化和索引优化(成本最低,效果最明显)
2. 缓存策略(有效减少数据库压力)
3. 表结构优化(需要评估影响)
4. 读写分离(架构调整)
5. 分库分表(最后的手段)

调优的核心原则是:先定位问题,再针对性优化,最后验证效果。通过系统化的诊断方法和多层次的优化策略,可以有效提升数据库性能。