30天性能优化实战:从代码到数据库的7大调优策略

张开发
2026/4/15 20:00:15 15 分钟阅读

分享文章

30天性能优化实战:从代码到数据库的7大调优策略
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毫秒// 优化前N1查询问题 for (Long skuId : skuIds) { Sku sku skuMapper.selectById(skuId); // 每次循环都查库 // ...处理逻辑 } // 优化后一次批量查询 ListSku skus skuMapper.selectBatchIds(skuIds); MapLong, 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;索引使用有三大禁忌最左匹配原则联合索引(a,b,c)只对a、ab、abc条件生效隐式转换陷阱varchar字段用数字查询会使索引失效避免过度索引每个额外索引会增加10%的写入开销批量插入也有讲究。曾优化过数据迁移脚本原来插入10万条要15分钟。采用三个技巧后降到30秒使用COPY替代INSERTPostgreSQL特有事务分批提交每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 MapLong, 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:ParallelGCThreads8 -XX:ConcGCThreads4 -XX:CMSInitiatingOccupancyFraction75 -XX:ExplicitGCInvokesConcurrent年轻代大小设置很关键。有个计算密集型应用默认配置下每分钟YGC 15次。调整NewRatio后YGC降到3次/分钟# 年轻代占比从1/3提高到1/2 -XX:NewRatio15. 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同时失效。最终用多级缓存解决本地缓存Caffeine5秒过期分布式缓存Redis30分钟过期数据库最终存储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服务器调优要点线上服务突然变慢时我习惯用这套检查清单CPUtop -H -p PID看哪个线程CPU高内存free -h观察swap使用情况IOiostat -x 1看await和%util网络ss -tnlp检查连接状态有一次ES集群响应变慢用vmstat 1发现cs上下文切换高达5万次/秒。调整线程池参数后恢复正常# Elasticsearch配置调整 thread_pool.search.size: 16 thread_pool.search.queue_size: 1000TCP参数优化也能带来惊喜。针对高并发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

更多文章