国家电网Java面试被问:慢查询的优化方案

一、问题定位与诊断

1. 识别慢查询

sql

复制

下载

复制代码
-- MySQL 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 2;  -- 2秒以上为慢查询
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- 查看慢查询统计
SHOW VARIABLES LIKE '%slow_query%';
SHOW STATUS LIKE '%Slow_queries%';

2. 分析执行计划(EXPLAIN)

sql

复制

下载

复制代码
EXPLAIN SELECT * FROM users WHERE age > 25 AND city = '北京';

EXPLAIN关键字段解读:

字段 说明 优化方向
type 访问类型 ALL→range→ref→eq_ref→const
key 实际使用的索引 确保使用合适索引
rows 扫描行数 尽量减少
Extra 额外信息 Using filesort, Using temporary 需要优化

二、索引优化方案

1. 创建合适索引

sql

复制

下载

复制代码
-- 单列索引
CREATE INDEX idx_age ON users(age);

-- 复合索引(最左前缀原则)
CREATE INDEX idx_city_age ON users(city, age);

-- 覆盖索引(索引包含所有查询字段)
CREATE INDEX idx_covering ON users(city, age, name);

-- 前缀索引(文本字段)
CREATE INDEX idx_email_prefix ON users(email(10));

2. 索引优化原则

  • 离散度高的列建索引(如性别就不适合)

  • 复合索引字段顺序:等值查询→范围查询→排序字段

  • 避免冗余索引:定期检查并删除无用索引

  • 索引字段尽量小:使用INT而非VARCHAR做主键

3. 索引失效场景

sql

复制

下载

复制代码
-- 1. 索引列使用函数或计算
SELECT * FROM users WHERE YEAR(create_time) = 2023; -- ❌
SELECT * FROM users WHERE create_time >= '2023-01-01'; -- ✅

-- 2. 隐式类型转换
SELECT * FROM users WHERE phone = 13800138000; -- ❌ phone是varchar
SELECT * FROM users WHERE phone = '13800138000'; -- ✅

-- 3. LIKE以通配符开头
SELECT * FROM users WHERE name LIKE '%张%'; -- ❌
SELECT * FROM users WHERE name LIKE '张%'; -- ✅

-- 4. 使用OR条件(除非所有列都有索引)
SELECT * FROM users WHERE age = 25 OR city = '北京'; -- ❌

三、SQL语句优化

1. 查询语句优化

sql

复制

下载

复制代码
-- ❌ 避免 SELECT *
SELECT * FROM users WHERE age > 25;

-- ✅ 只取需要的字段
SELECT id, name, age FROM users WHERE age > 25;

-- ❌ 避免在WHERE子句中使用 != 或 <>
SELECT * FROM orders WHERE status != 'completed';

-- ✅ 考虑使用范围查询
SELECT * FROM orders WHERE status IN ('pending', 'processing');

-- ❌ 避免全表扫描的大偏移量分页
SELECT * FROM users LIMIT 1000000, 20;

-- ✅ 使用覆盖索引+子查询优化分页
SELECT * FROM users WHERE id > 
  (SELECT id FROM users ORDER BY id LIMIT 1000000, 1) 
LIMIT 20;

2. JOIN优化

sql

复制

下载

复制代码
-- ❌ 笛卡尔积
SELECT * FROM users, orders;

-- ✅ 明确JOIN条件
SELECT * FROM users u 
JOIN orders o ON u.id = o.user_id;

-- 小表驱动大表
SELECT * FROM small_table s
JOIN large_table l ON s.id = l.small_id;

-- 避免子查询(可转为JOIN)
SELECT * FROM users 
WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);

-- 改为JOIN
SELECT DISTINCT u.* FROM users u
JOIN orders o ON u.id = o.user_id AND o.amount > 100;

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

3. 批量操作优化

sql

复制

下载

复制代码
-- ❌ 多次单条插入
INSERT INTO users(name) VALUES ('张三');
INSERT INTO users(name) VALUES ('李四');

-- ✅ 批量插入
INSERT INTO users(name) VALUES ('张三'), ('李四'), ('王五');

-- ❌ 循环更新
UPDATE users SET score = score + 1 WHERE id = 1;
UPDATE users SET score = score + 1 WHERE id = 2;

-- ✅ 批量更新
UPDATE users SET score = score + 1 WHERE id IN (1, 2, 3);

四、数据库设计优化

1. 表结构设计

sql

复制

下载

复制代码
-- 规范化设计(减少冗余)
-- 但适度反规范化提升查询性能
CREATE TABLE orders (
  id BIGINT PRIMARY KEY,
  user_id BIGINT,
  -- 反规范化:存储用户名,避免JOIN
  user_name VARCHAR(50),
  amount DECIMAL(10, 2),
  INDEX idx_user_id(user_id)
);

-- 垂直拆分大表
-- 将不常用的大字段拆分到扩展表
CREATE TABLE articles (
  id BIGINT PRIMARY KEY,
  title VARCHAR(200),
  summary TEXT,
  created_at DATETIME
);

CREATE TABLE article_contents (
  article_id BIGINT PRIMARY KEY,
  content LONGTEXT,  -- 大字段单独存储
  FOREIGN KEY (article_id) REFERENCES articles(id)
);

2. 数据类型优化

sql

复制

下载

复制代码
-- 使用合适的数据类型
TINYINT(1) -- 代替布尔值
INT UNSIGNED -- 非负数
DECIMAL(10, 2) -- 精确小数
VARCHAR(255) -- 避免过大

-- 使用ENUM代替字符串
status ENUM('active', 'inactive', 'pending')

-- 时间戳使用TIMESTAMP(4字节)而非DATETIME(8字节)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

五、查询缓存与预处理

1. 应用层缓存

java

复制

下载

复制代码
// Redis缓存查询结果
public User getUserById(Long id) {
    String key = "user:" + id;
    String cached = redis.get(key);
    if (cached != null) {
        return JSON.parseObject(cached, User.class);
    }
    
    User user = userDao.getById(id);
    redis.setex(key, 3600, JSON.toJSONString(user));
    return user;
}

2. 数据库查询缓存

sql

复制

下载

复制代码
-- MySQL查询缓存(MySQL 8.0已移除)
-- 使用应用层缓存替代

-- 使用预处理语句
PREPARE stmt FROM 'SELECT * FROM users WHERE age > ? AND city = ?';
SET @age = 25, @city = '北京';
EXECUTE stmt USING @age, @city;
DEALLOCATE PREPARE stmt;

六、分库分表策略

1. 水平分表

sql

复制

下载

复制代码
-- 按用户ID取模分表
CREATE TABLE users_0 LIKE users_template;
CREATE TABLE users_1 LIKE users_template;
-- ...创建users_0到users_9

-- 路由规则
table_suffix = user_id % 10;
sql = "SELECT * FROM users_" + table_suffix + " WHERE id = ?";

2. 分库分表中间件

yaml

复制

下载

复制代码
# ShardingSphere配置示例
spring:
  shardingsphere:
    datasource:
      names: ds0, ds1
    sharding:
      tables:
        orders:
          actual-data-nodes: ds$->{0..1}.orders_$->{0..15}
          table-strategy:
            inline:
              sharding-column: order_id
              algorithm-expression: orders_$->{order_id % 16}
          database-strategy:
            inline:
              sharding-column: user_id
              algorithm-expression: ds$->{user_id % 2}

七、读写分离与负载均衡

1. 主从架构

sql

复制

下载

复制代码
-- 主库写操作
INSERT INTO orders (...) VALUES (...);

-- 从库读操作
SELECT * FROM orders WHERE ...;  -- 读请求路由到从库

-- 同步延迟处理方案
-- 1. 写后立即读,强制走主库
-- 2. 关键业务读主库
-- 3. 容忍秒级延迟

2. 连接池配置

java

复制

下载

复制代码
// HikariCP配置
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://master:3306/db");
config.setMaximumPoolSize(20);
config.setMinimumIdle(10);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);

// 读写分离数据源
@Bean
@Primary
public DataSource dataSource() {
    Map<Object, Object> targetDataSources = new HashMap<>();
    targetDataSources.put("master", masterDataSource());
    targetDataSources.put("slave", slaveDataSource());
    
    AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
        @Override
        protected Object determineCurrentLookupKey() {
            return TransactionSynchronizationManager.isCurrentTransactionReadOnly() 
                ? "slave" : "master";
        }
    };
    routingDataSource.setTargetDataSources(targetDataSources);
    return routingDataSource;
}

八、监控与调优工具

1. 监控工具集

bash

复制

下载

复制代码
# 1. 实时监控
mysqladmin -u root -p extended-status | grep -i "slow"

# 2. 分析慢查询日志
mysqldumpslow -s t /var/log/mysql/slow.log

# 3. Percona Toolkit分析
pt-query-digest /var/log/mysql/slow.log

# 4. 实时诊断
SHOW PROCESSLIST;  -- 查看当前连接
SHOW ENGINE INNODB STATUS;  -- InnoDB状态

2. 性能分析流程

sql

复制

下载

复制代码
-- 1. 找出慢查询
SELECT * FROM mysql.slow_log 
WHERE query_time > 5 
ORDER BY query_time DESC 
LIMIT 10;

-- 2. 分析执行计划
EXPLAIN FORMAT=JSON SELECT ...;

-- 3. 优化建议
SELECT * FROM sys.schema_unused_indexes;  -- 未使用索引
SELECT * FROM sys.statements_with_full_table_scans;  -- 全表扫描

九、实战案例

案例1:电商订单查询优化

sql

复制

下载

复制代码
-- 原始慢查询(执行时间:8.2s)
SELECT * FROM orders 
WHERE user_id = 123 
  AND status IN (1, 2, 3) 
  AND create_time > '2023-01-01'
ORDER BY create_time DESC 
LIMIT 0, 20;

-- 优化步骤:
-- 1. 创建复合索引
CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time);

-- 2. 优化SQL(使用覆盖索引)
SELECT id, order_no, amount, status, create_time 
FROM orders 
WHERE user_id = 123 
  AND status IN (1, 2, 3) 
  AND create_time > '2023-01-01'
ORDER BY create_time DESC 
LIMIT 0, 20;

-- 3. 结果:执行时间降至0.02s

案例2:大数据量表的分页优化

sql

复制

下载

复制代码
-- 原始:LIMIT 1000000, 20(很慢)
SELECT * FROM user_actions ORDER BY id LIMIT 1000000, 20;

-- 优化1:使用覆盖索引
SELECT * FROM user_actions 
WHERE id >= (SELECT id FROM user_actions ORDER BY id LIMIT 1000000, 1)
ORDER BY id LIMIT 20;

-- 优化2:记录上次查询的最大ID
-- 第一页
SELECT * FROM user_actions ORDER BY id LIMIT 20; -- 记下最后一条id=20
-- 第二页
SELECT * FROM user_actions WHERE id > 20 ORDER BY id LIMIT 20;

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

十、预防措施与最佳实践

1. 开发规范

sql

复制

下载

复制代码
-- SQL编写规范
-- 1. 禁止SELECT *
-- 2. UPDATE/DELETE必须有WHERE条件
-- 3. 大批量操作分批进行
-- 4. 重要查询必须review执行计划
-- 5. 定期清理无用索引和历史数据

2. 定期维护

sql

复制

下载

复制代码
-- 每周执行
OPTIMIZE TABLE big_table;  -- 整理碎片
ANALYZE TABLE important_table;  -- 更新统计信息

-- 每天监控
-- 1. 慢查询数量增长
-- 2. 索引使用情况
-- 3. 锁等待时间
-- 4. 连接数监控

3. 应急预案

sql

复制

下载

复制代码
-- 当出现性能问题时:
-- 1. 立即kill慢查询
SHOW PROCESSLIST;
KILL [query_id];

-- 2. 临时增加索引
CREATE INDEX idx_temp ON problem_table(problem_column);

-- 3. 修改SQL_HINT强制使用索引
SELECT /*+ INDEX(table_name index_name) */ * FROM table_name;

-- 4. 降级方案:返回部分数据或缓存数据

总结:慢查询优化检查清单

  1. 是否使用EXPLAIN分析执行计划?

  2. 是否创建了合适的索引?

  3. 是否避免了索引失效场景?

  4. 是否使用覆盖索引?

  5. 是否优化了JOIN查询?

  6. 是否优化了分页查询?

  7. ✅ **是否避免了SELECT ***?

  8. 是否使用批量操作?

  9. 表结构设计是否合理?

  10. 是否使用了读写分离?

  11. 是否有缓存策略?

  12. 是否有监控和告警?

优化顺序建议

  1. 优先优化:索引设计、SQL语句

  2. 其次优化:数据库参数、硬件升级

  3. 最后考虑:架构改造(分库分表、读写分离)

记住:80%的性能问题可以通过优化索引和SQL解决,剩下的20%才需要考虑架构调整。

相关推荐
@小码农2 小时前
202512 电子学会 Scratch图形化编程等级考试四级真题(附答案)
java·开发语言·算法
ejjdhdjdjdjdjjsl2 小时前
C#类型转换与异常处理全解析
开发语言·c#
程序猿ZhangSir2 小时前
深入理解 BIO,NIO,AIO 三者的用途和区别?Select,poll,epoll 操作系统函数简介
java·spring·nio
智航GIS2 小时前
6.2 while循环
java·前端·python
2201_757830872 小时前
AOP核心概念
java·前端·数据库
为所欲为、Lynn2 小时前
用FastJson的Filter自动映射枚举
java·spring boot
qq_336313932 小时前
java基础-IO流(转换流)
java·开发语言·python
小宇的天下2 小时前
Calibre nmDRC 运行机制与规则文件(13-2)
运维·开发语言
雪人.2 小时前
JavaWeb经典面试题
java·服务器·前端·java面试题