30天性能优化实战:从代码到数据库的7大调优策略
2026/4/17 23:10:09 网站建设 项目流程

1. 代码层面的性能优化实战

第一次接手老项目时,我被一个简单的分页查询接口震惊了——响应时间竟然超过5秒!翻开代码一看,发现开发者在for循环里疯狂拼接字符串,还用+号操作符。这种初级错误就像用勺子挖隧道,效率低得让人抓狂。

字符串拼接的正确姿势其实很简单。实测下来,StringBuilder比+号快20倍以上。特别是在处理日志拼接、SQL拼接等场景时,建议预分配足够容量:

// 错误示范:每次循环都创建新对象 String result = ""; for (int i = 0; i < 10000; i++) { result += "data" + i; } // 正确做法:预分配缓冲区 StringBuilder builder = new StringBuilder(50000); for (int i = 0; i < 10000; i++) { builder.append("data").append(i); }

更隐蔽的性能杀手是循环体内的数据库查询。上周优化过一个商品详情接口,原来需要3秒,只因开发者在循环里查了N次商品SKU表。改造为批量查询后,直接降到200毫秒:

// 优化前:N+1查询问题 for (Long skuId : skuIds) { Sku sku = skuMapper.selectById(skuId); // 每次循环都查库 // ...处理逻辑 } // 优化后:一次批量查询 List<Sku> skus = skuMapper.selectBatchIds(skuIds); Map<Long, Sku> skuMap = skus.stream().collect(Collectors.toMap(Sku::getId, v -> v)); for (Long skuId : skuIds) { Sku sku = skuMap.get(skuId); // 内存操作 // ...处理逻辑 }

2. 数据库查询的黄金法则

执行计划是DBA的听诊器。有次排查慢查询,发现个有趣现象:同样的SQL,测试环境跑0.1秒,生产环境要8秒。用EXPLAIN ANALYZE一看,生产环境漏了索引,导致150万行全表扫描。加上索引后,查询直接起飞:

-- 优化前:全表扫描 EXPLAIN ANALYZE SELECT * FROM orders WHERE create_time > '2023-01-01'; -- 优化后:索引扫描 CREATE INDEX idx_orders_create_time ON orders(create_time); EXPLAIN ANALYZE SELECT * FROM orders WHERE create_time > '2023-01-01';

索引使用有三大禁忌:

  1. 最左匹配原则:联合索引(a,b,c)只对a、ab、abc条件生效
  2. 隐式转换陷阱:varchar字段用数字查询会使索引失效
  3. 避免过度索引:每个额外索引会增加10%的写入开销

批量插入也有讲究。曾优化过数据迁移脚本,原来插入10万条要15分钟。采用三个技巧后降到30秒:

  • 使用COPY替代INSERT(PostgreSQL特有)
  • 事务分批提交(每5000条commit一次)
  • 临时禁用索引和约束
-- 高性能批量插入示范 BEGIN; ALTER TABLE orders DISABLE TRIGGER ALL; -- 禁用触发器 COPY orders FROM '/data/orders.csv' WITH CSV; ALTER TABLE orders ENABLE TRIGGER ALL; -- 恢复触发器 COMMIT;

3. 并发编程的锁优化艺术

多线程环境下,锁竞争是性能头号杀手。去年优化过支付系统,TPS卡在200上不去。用arthas监控发现,80%线程阻塞在订单状态锁上。通过锁细化改造,最终提升到1200TPS:

// 优化前:粗粒度锁 public synchronized void processPayment(Order order) { // 20行业务逻辑 } // 优化后:细粒度锁 private final Map<Long, Object> orderLocks = new ConcurrentHashMap<>(); public void processPayment(Order order) { Object lock = orderLocks.computeIfAbsent(order.getId(), k -> new Object()); synchronized (lock) { // 只锁当前订单 // 20行业务逻辑 } }

读写分离是另一个利器。在配置中心项目中,用ReadWriteLock使读性能提升5倍:

private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); public String getConfig(String key) { rwLock.readLock().lock(); try { return configMap.get(key); } finally { rwLock.readLock().unlock(); } } public void updateConfig(String key, String value) { rwLock.writeLock().lock(); try { configMap.put(key, value); } finally { rwLock.writeLock().unlock(); } }

4. JVM调优实战手册

内存泄漏排查就像破案。有次线上服务频繁Full GC,用MAT分析heap dump后,发现是ThreadLocal未清理导致的。这类问题有典型特征:

  • Old区内存持续增长
  • GC后内存不下降
  • 线程数异常增多

JVM参数配置要量体裁衣。电商大促前,我们这样调整CMS参数:

-XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=8 -XX:ConcGCThreads=4 -XX:CMSInitiatingOccupancyFraction=75 -XX:+ExplicitGCInvokesConcurrent

年轻代大小设置很关键。有个计算密集型应用,默认配置下每分钟YGC 15次。调整NewRatio后,YGC降到3次/分钟:

# 年轻代占比从1/3提高到1/2 -XX:NewRatio=1

5. SQL语句的魔鬼细节

同样的查询条件,不同写法性能可能差百倍。分享几个真实案例:

分页优化

-- 低效写法(全表扫描) SELECT * FROM orders ORDER BY id LIMIT 1000000, 20; -- 高效写法(索引扫描) SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20;

JOIN优化

-- 错误示范:使用OR条件 SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.status = 1 OR o.amount > 1000; -- 正确做法:UNION ALL拆分 SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE u.status = 1 UNION ALL SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE o.amount > 1000 AND u.status != 1

隐式转换问题

/* phone是varchar字段但存数字 */ EXPLAIN SELECT * FROM customers WHERE phone = 13800138000; -- 全表扫描 EXPLAIN SELECT * FROM customers WHERE phone = '13800138000'; -- 走索引

6. 缓存使用的正确姿势

缓存击穿是高频面试题。去年双11,商品详情页突现大量503,就是因为热点key同时失效。最终用多级缓存解决:

  1. 本地缓存(Caffeine):5秒过期
  2. 分布式缓存(Redis):30分钟过期
  3. 数据库:最终存储
public Product getProduct(Long id) { // 一级缓存查询 Product product = caffeineCache.get(id, k -> { // 二级缓存查询 return redisTemplate.opsForValue().get("product:" + id) .orElseGet(() -> dbMapper.selectById(id)); }); return product; }

缓存更新策略也很关键。我们采用「先更新库再删缓存」策略,配合消息队列重试:

@Transactional public void updateProduct(Product product) { // 1. 更新数据库 productMapper.updateById(product); // 2. 删除缓存 redisTemplate.delete("product:" + product.getId()); // 3. 发延迟消息(防删除失败) mqTemplate.sendDelayMessage("cache-refresh", product.getId(), 5, TimeUnit.SECONDS); }

7. Linux服务器调优要点

线上服务突然变慢时,我习惯用这套检查清单:

  1. CPUtop -H -p <PID>看哪个线程CPU高
  2. 内存free -h观察swap使用情况
  3. IOiostat -x 1看await和%util
  4. 网络ss -tnlp检查连接状态

有一次ES集群响应变慢,用vmstat 1发现cs(上下文切换)高达5万次/秒。调整线程池参数后恢复正常:

# Elasticsearch配置调整 thread_pool.search.size: 16 thread_pool.search.queue_size: 1000

TCP参数优化也能带来惊喜。针对高并发HTTP服务,我们调整了这些内核参数:

# 增大连接队列 echo 4096 > /proc/sys/net/core/somaxconn # 加快TIME_WAIT回收 echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle # 扩大端口范围 echo "1024 65000" > /proc/sys/net/ipv4/ip_local_port_range

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询