013、数据库性能优化:索引、查询与连接池

013、数据库性能优化:索引、查询与连接池


一、从一次慢查询说起

上周排查一个线上问题,接口响应时间从平均 50ms 飙到 2 秒以上。打开慢查询日志,抓到一条 SQL:

sql 复制代码
SELECT * FROM order_detail 
WHERE user_id = 10086 
  AND status = 1 
  AND create_time > '2024-01-01'
ORDER BY update_time DESC 
LIMIT 20;

表里有 300 万条数据,这条查询居然扫了 280 万行。第一反应是索引问题,一看表结构,果然只在 id 上建了主键索引。问题就出在这里------没有为查询条件建立合适的索引。


二、索引不是越多越好

很多人觉得索引能加速查询,就拼命建索引。我见过一张表建了 15 个索引,写入速度慢得像蜗牛。索引的本质是用空间换时间,每个索引都是一棵 B+ 树,写入时要维护所有索引树。

踩坑案例 :曾经在 status 字段上单独建索引,查询依然慢。后来明白,status 只有 0/1 两种值,区分度太低,MySQL 优化器可能直接忽略这个索引。

正确姿势:对上面那条慢查询,我建的是联合索引:

sql 复制代码
ALTER TABLE order_detail ADD INDEX idx_user_status_time (user_id, status, create_time);

注意字段顺序:

  • user_id 放第一,因为等值查询,区分度高
  • status 放第二,虽然区分度低,但能利用索引过滤
  • create_time 放最后,用于范围查询

别这样写:

sql 复制代码
-- 顺序乱放,索引可能失效
ADD INDEX idx_time_status_user (create_time, status, user_id);

三、查询优化的几个细节

1. 避免 SELECT *

sql 复制代码
-- 坏习惯:读所有字段
SELECT * FROM large_table WHERE ...

-- 好习惯:只取需要的
SELECT id, name, status FROM large_table WHERE ...

多一个字段就可能多一次回表操作,尤其 TEXT/BLOB 字段。

2. 小心 JOIN 陷阱

sql 复制代码
-- 大表 JOIN 大表,性能灾难
SELECT * FROM table_a a 
JOIN table_b b ON a.id = b.a_id 
WHERE a.create_time > '2024-01-01';

-- 先过滤再 JOIN
SELECT * FROM (
  SELECT id, name FROM table_a 
  WHERE create_time > '2024-01-01'
) a 
JOIN table_b b ON a.id = b.a_id;

3. 分页的坑

sql 复制代码
-- 深度分页,越往后越慢
SELECT * FROM table LIMIT 1000000, 20;

-- 优化:记住上一页最后一条的 id
SELECT * FROM table WHERE id > 1000000 LIMIT 20;

四、连接池:别小看这池子水

早期项目直接每次查询创建连接,QPS 到 500 就报 Too many connections。后来上了连接池,稳定支撑 3000+ QPS。

关键参数配置(以 HikariCP 为例):

yaml 复制代码
# 这里踩过坑:maxLifetime 设太短会导致连接频繁重建
hikari:
  maximum-pool-size: 20      # 根据业务调整,不是越大越好
  minimum-idle: 10
  max-lifetime: 1800000      # 30分钟,别低于 30s
  connection-timeout: 3000   # 获取连接超时时间
  idle-timeout: 600000       # 空闲连接超时

监控指标要看:

  • 活跃连接数
  • 空闲连接数
  • 等待获取连接的线程数
  • 连接创建/销毁频率

五、ORM 的甜与苦

用 Django ORM 或 SQLAlchemy 确实方便,但生成的 SQL 可能很蠢。一定要开启 query log,定期检查。

案例:

python 复制代码
# 这样写会产生 N+1 查询
users = User.objects.filter(age__gt=20)
for user in users:
    print(user.profile.address)  # 每次循环都查一次 profile

# 优化:使用 select_related
users = User.objects.select_related('profile').filter(age__gt=20)

六、个人经验包

  1. 索引创建后要验证 :用 EXPLAIN 看执行计划,关注 type 字段(至少 range 以上),rows 越小越好。

  2. 定期清理慢查询:每周看一次慢查询日志,超过 200ms 的都要分析。

  3. 连接池不是银弹 :连接数设置要考虑数据库最大连接数(max_connections),留 20% 余量给管理连接。

  4. 冷热数据分离:3 个月前的订单数据归档到历史表,主表只留热数据。

  5. 压测时关注数据库:CPU 使用率超过 70% 或连接数飙高,就要考虑优化了。

  6. 开发环境用真实数据量测试:用 100 条数据测不出性能问题,至少灌 10 万条。


数据库优化是个细致活,需要持续观察和调整。最好的优化时机是设计阶段,最贵的优化时机是上线以后。先理清业务查询模式,再动手加索引,别凭感觉来。

相关推荐
quantdash_cc5 分钟前
Python 获取实时行情后如何进行批量筛选?从全市场快照到策略候选池
开发语言·python·数据分析·量化交易·股票数据·quantdash
AI 算法大模型备案~当当9 分钟前
各地备案数量怎么看:一份属地公告的认读与台账方法
java·数据库·人工智能
小蜗 strong22 分钟前
和电脑猜拳(随机程序应用)
服务器·前端·python
梦帮科技36 分钟前
量子张量网络破局大模型:从矩阵乘积态 (MPS) 到张量列 (TT-SVD) 低秩收缩全推导
网络·数据结构·数据库·线性代数·矩阵·架构·模拟退火算法
曹牧1 小时前
PL/SQL Developer:导出建表语句
数据库·sql
念念不忘 必有回响1 小时前
MySQL 事务与隔离级别完全指南
数据库·mysql
华研前沿标杆游学1 小时前
企业标杆游学|走进东莞OPPO总部✨探秘智造与品牌出海
python
霸道流氓气质1 小时前
LLM 应用限流与熔断机制完全指南:从多层防护架构到Java生产级弹性实战
java·开发语言·架构
大侠归来1 小时前
C语言内存管理:从栈到堆的完整指南
c语言·开发语言·python
lusklusklusk1 小时前
Sqlserver_Oracle_Mysql_Postgresql表_索引碎片或膨胀的知识点汇总
mysql·oracle·sqlserver