|
38 | 38 | <dependency> |
39 | 39 | <groupId>io.github.chenjunwenhao</groupId> |
40 | 40 | <artifactId>mybatis-sql-optimizer-spring-boot-starter</artifactId> |
41 | | - <version>1.2.10</version><!-- 最新版本 --> |
| 41 | + <version>1.2.11</version><!-- 最新版本 --> |
42 | 42 | </dependency> |
43 | 43 | ``` |
44 | 44 |
|
@@ -162,6 +162,277 @@ public class CustomReporter implements SqlAnalysisReporter { |
162 | 162 | 2025-04-04 19:53:59 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - - 警告: 对列 `AREA_NAME` 使用函数 `UPPER()`,可能导致索引失效。白名单函数: [ABS, FLOOR, COALESCE, CEILING, ROUND, NULLIF] |
163 | 163 | 2025-04-04 19:53:59 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - - 全表扫描JOIN操作检测到,考虑添加适当的索 |
164 | 164 | ``` |
| 165 | +### 5. 对接大模型进行AI分析 |
| 166 | +由于提供了自定义规则扩展点,目前`mybatis-sql-optimizer-spring-boot-starter`就不对大模型进行集成了,如果有大模型的条件可以自己自定义拓展,可以参考 `DeepSeekAdvice` 实现。 |
| 167 | +#### 大模型拓展点效果 |
| 168 | +通过自定义规则拓展点,实现 `SqlOptimizationAdvice` 接口创建自定义规则,可以对接大模型,如:ChatGPT、LLM、DeepSeek等,调用大模型进行SQL优化分析, 从而实现对SQL的优化。我自己使用了DeepSeek的API进行了测试,效果不错。 |
| 169 | +先看效果 |
| 170 | +```java |
| 171 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter -===== SQL分析报告 [MySQL:com.faq.mapper.DictDao.getCity] ===== |
| 172 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter -SQL: SELECT |
| 173 | + think_areas.area_id as id, |
| 174 | + think_areas.parent_id as parentId, |
| 175 | + think_areas.area_name as label, |
| 176 | + think_areas.area_type as type |
| 177 | + FROM |
| 178 | + think_areas |
| 179 | + WHERE |
| 180 | + think_areas.parent_id = ? |
| 181 | + and think_areas.area_type like '%1%' |
| 182 | + or upper(think_areas.area_name) like '%2%' |
| 183 | + or Upper(think_areas.area_name) like '%2%' |
| 184 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter -执行时间: 310ms |
| 185 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter -执行计划: |
| 186 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - filtered: 100.0 |
| 187 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - Extra: Using where |
| 188 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - select_type: SIMPLE |
| 189 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - id: 1 |
| 190 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - type: ALL |
| 191 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - rows: 3408 |
| 192 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - table: think_areas |
| 193 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter -优化建议: |
| 194 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - - 检测到全表扫描,建议为表 think_areas 添加索引 |
| 195 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - - 索引选择性不足,索引 null 过滤了100.0%数据,建议优化索引或查询条件 |
| 196 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - - LIKE条件以通配符开头,无法使用索引 |
| 197 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - - 警告: 对列 `AREA_NAME` 使用函数 `UPPER()`,可能导致索引失效。白名单函数: [ABS, FLOOR, COALESCE, CEILING, ROUND, NULLIF] |
| 198 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - - 警告: 对列 `AREA_NAME` 使用函数 `UPPER()`,可能导致索引失效。白名单函数: [ABS, FLOOR, COALESCE, CEILING, ROUND, NULLIF] |
| 199 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - - 全表扫描JOIN操作检测到,考虑添加适当的索引 |
| 200 | +2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - - [ai] 原SQL分析结果: # SQL 优化分析 |
| 201 | + |
| 202 | +## 当前SQL存在的问题 |
| 203 | + |
| 204 | +1. **索引失效问题**: |
| 205 | + - `like '%1%'` 和 `like '%2%'` 使用了前导通配符,导致无法使用索引 |
| 206 | + - `upper()` 函数的使用也会导致索引失效 |
| 207 | + |
| 208 | +2. **逻辑错误**: |
| 209 | + - WHERE条件中的逻辑运算符优先级问题,当前写法等同于: |
| 210 | + ```sql |
| 211 | + (think_areas.parent_id = ? and think_areas.area_type like '%1%') |
| 212 | + or upper(think_areas.area_name) like '%2%' |
| 213 | + or Upper(think_areas.area_name) like '%2%' |
| 214 | + ``` |
| 215 | + - 最后一个条件与倒数第二个条件重复 |
| 216 | + |
| 217 | +3. **性能问题**: |
| 218 | + - 全表扫描不可避免 |
| 219 | + - 重复条件计算 |
| 220 | + |
| 221 | +## 优化建议 |
| 222 | + |
| 223 | +### 1. 修正逻辑错误 |
| 224 | + |
| 225 | +SELECT |
| 226 | + think_areas.area_id as id, |
| 227 | + think_areas.parent_id as parentId, |
| 228 | + think_areas.area_name as label, |
| 229 | + think_areas.area_type as type |
| 230 | +FROM |
| 231 | + think_areas |
| 232 | +WHERE |
| 233 | + think_areas.parent_id = ? |
| 234 | + AND (think_areas.area_type like '%1%' |
| 235 | + OR upper(think_areas.area_name) like '%2%') |
| 236 | + |
| 237 | +### 2. 更好的优化方案 |
| 238 | + |
| 239 | +如果业务允许,尽量避免使用前导通配符: |
| 240 | + |
| 241 | +SELECT |
| 242 | + think_areas.area_id as id, |
| 243 | + think_areas.parent_id as parentId, |
| 244 | + think_areas.area_name as label, |
| 245 | + think_areas.area_type as type |
| 246 | +FROM |
| 247 | + think_areas |
| 248 | +WHERE |
| 249 | + think_areas.parent_id = ? |
| 250 | + AND (think_areas.area_type like '1%' -- 去掉前导通配符 |
| 251 | + OR think_areas.area_name like '2%') -- 去掉UPPER函数和前导通配符 |
| 252 | + |
| 253 | + |
| 254 | +### 3. 索引建议 |
| 255 | + |
| 256 | +如果这是高频查询,建议添加以下索引: |
| 257 | + |
| 258 | +CREATE INDEX idx_parent_id ON think_areas(parent_id); |
| 259 | +CREATE INDEX idx_area_type ON think_areas(area_type); |
| 260 | +CREATE INDEX idx_area_name ON think_areas(area_name); |
| 261 | + |
| 262 | + |
| 263 | +### 4. 其他建议 |
| 264 | + |
| 265 | +1. 如果数据量大且查询频繁,考虑使用全文索引 |
| 266 | +2. 考虑将大小写敏感的需求移到应用层处理 |
| 267 | +3. 如果`area_type`有固定值,使用`=`代替`like` |
| 268 | + |
| 269 | +## 最终优化SQL |
| 270 | + |
| 271 | +SELECT |
| 272 | + area_id as id, |
| 273 | + parent_id as parentId, |
| 274 | + area_name as label, |
| 275 | + area_type as type |
| 276 | +FROM |
| 277 | + think_areas |
| 278 | +WHERE |
| 279 | + parent_id = ? |
| 280 | + AND (area_type like '1%' |
| 281 | + OR area_name like '2%') |
| 282 | + |
| 283 | + |
| 284 | +这个优化版本: |
| 285 | +1. 移除了重复条件 |
| 286 | +2. 修正了逻辑运算符优先级 |
| 287 | +3. 简化了表名前缀 |
| 288 | +4. 尽可能避免前导通配符 |
| 289 | +5. 移除了不必要的UPPER函数 |
| 290 | + 2025-04-12 20:44:09 [pool-2-thread-1] INFO com.wuya.mybatis.optimizer.report.DefaultAnalysisReporter - - [ai] 执行计划分析结果: # SQL 分析报告 |
| 291 | + |
| 292 | +## 当前SQL执行情况分析 |
| 293 | + |
| 294 | +从提供的执行计划信息来看,这个SQL查询存在明显的性能问题: |
| 295 | + |
| 296 | +1. **访问类型(type)**: `ALL` - 表示进行了全表扫描,这是最差的一种访问方式 |
| 297 | +2. **扫描行数(rows)**: 3408 - 需要扫描整个表的3408行数据 |
| 298 | +3. **过滤条件(filtered)**: 100% - 没有有效利用索引进行过滤 |
| 299 | +4. **额外信息(Extra)**: `Using where` - 表示在存储引擎检索行后进行了额外的过滤 |
| 300 | + |
| 301 | +## 优化建议 |
| 302 | + |
| 303 | +### 1. 添加适当的索引 |
| 304 | + |
| 305 | +这是最关键的优化点。根据查询条件,为`think_areas`表添加合适的索引: |
| 306 | + |
| 307 | +-- 假设查询中有WHERE条件字段为area_name |
| 308 | +ALTER TABLE think_areas ADD INDEX idx_area_name(area_name); |
| 309 | + |
| 310 | +-- 如果是多条件查询,考虑复合索引 |
| 311 | +ALTER TABLE think_areas ADD INDEX idx_multiple(column1, column2); |
| 312 | + |
| 313 | + |
| 314 | +### 2. 检查查询条件 |
| 315 | + |
| 316 | +确保WHERE条件使用了索引列,避免在索引列上使用函数或计算: |
| 317 | + |
| 318 | +-- 不好的写法(无法使用索引) |
| 319 | +SELECT * FROM think_areas WHERE YEAR(create_time) = 2023; |
| 320 | + |
| 321 | +-- 好的写法 |
| 322 | +SELECT * FROM think_areas WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'; |
| 323 | + |
| 324 | + |
| 325 | +### 3. 限制返回的列 |
| 326 | + |
| 327 | +避免使用`SELECT *`,只查询需要的列: |
| 328 | + |
| 329 | +-- 替代 |
| 330 | +SELECT id, area_name FROM think_areas WHERE ...; |
| 331 | + |
| 332 | +### 4. 考虑表分区 |
| 333 | + |
| 334 | +如果表数据量很大(远大于3408行),可以考虑按某些条件进行分区。 |
| 335 | + |
| 336 | +### 5. 检查表结构 |
| 337 | + |
| 338 | +确保表有合适的主键,字段类型选择合理,避免使用过大的字段类型。 |
| 339 | + |
| 340 | +## 实施建议 |
| 341 | + |
| 342 | +1. 首先分析实际查询语句(当前只提供了执行计划,缺少SQL文本) |
| 343 | +2. 根据实际查询条件创建针对性索引 |
| 344 | +3. 使用EXPLAIN验证优化效果 |
| 345 | +4. 考虑在测试环境验证后再应用到生产环境 |
| 346 | + |
| 347 | +需要更具体的优化建议,请提供完整的SQL查询语句和表结构信息。 |
| 348 | +``` |
| 349 | +#### 伪代码示例 |
| 350 | +下面是一个AIAdvice的伪代码示例,DeepSeekClient是通过HTTP调用的DeepSeek的API,它实现了SqlOptimizationAdvice接口,用于生成SQL优化建议。 |
| 351 | +```java |
| 352 | +import org.springframework.beans.factory.annotation.Autowired; |
| 353 | + |
| 354 | +@Component |
| 355 | +public class DeepSeekAdvice implements SqlOptimizationAdvice { |
| 356 | + |
| 357 | + @Autowired |
| 358 | + private DeepSeekClient deepseekClient; |
| 359 | + /** |
| 360 | + * 生成优化建议 |
| 361 | + * @param explainResult |
| 362 | + * @return |
| 363 | + */ |
| 364 | + @Override |
| 365 | + public List<String> generateAdvice(SqlExplainResult explainResult) { |
| 366 | + // 自定义分析逻辑 |
| 367 | + List<String> adviceList = new ArrayList(); |
| 368 | + // 原sql |
| 369 | + String sql = explainResult.getSql(); |
| 370 | + // 执行计划结果 |
| 371 | + List<Map<String, Object>> explainResults = explainResult.getExplainResults(); |
| 372 | + // 模型分析原sql; 伪代码DeepSeek API |
| 373 | + String sqlAIAdvice = deepseekClient.analysis(sql); |
| 374 | + // 模型分析执行计划 |
| 375 | + String sqlExplainAIAdvice = deepseekClient.analysis(explainResults); |
| 376 | + adviceList.add("[ai] 原SQL分析结果:" + sqlAIAdvice); |
| 377 | + adviceList.add("[ai] 执行计划分析结果:" + sqlExplainAIAdvice); |
| 378 | + return adviceList; |
| 379 | + } |
| 380 | + |
| 381 | + /** |
| 382 | + * 是否支持该数据库类型 |
| 383 | + * @param dbType |
| 384 | + * @return |
| 385 | + */ |
| 386 | + @Override |
| 387 | + public boolean supports(DatabaseType dbType) { |
| 388 | + // 只支持PostgreSQL |
| 389 | + // 如果规则不区分数据库,直接return true; |
| 390 | + return true; |
| 391 | + } |
| 392 | +} |
| 393 | +``` |
| 394 | +#### DeepSeek的模型选择对比和输出markdown解决方案 |
| 395 | + |
| 396 | +--- |
| 397 | +**模型选择对比** |
| 398 | + |
| 399 | +|模型名称|适用场景|输出特点| |
| 400 | +|-|-|-| |
| 401 | +|`deepseek-chat`|通用对话场景|倾向于自然语言+Markdown| |
| 402 | +|`deepseek-coder`|代码生成/分析/优化|结构化代码+技术术语| |
| 403 | +|`deepseek-math`|数学/逻辑分析|公式/符号化表达| |
| 404 | + |
| 405 | + |
| 406 | +--- |
| 407 | + |
| 408 | + **性能对比测试数据** |
| 409 | + |
| 410 | +| 模型 | 响应时间(avg) | 技术术语准确率 | 格式合规性 | |
| 411 | +|----------------|--------------|---------------|-----------| |
| 412 | +| deepseek-chat | 1.2s | 78% | 需后处理 | |
| 413 | +| deepseek-coder | 0.9s | 95% | 直接可用 | |
| 414 | + |
| 415 | +--- |
| 416 | + **输出markdown解决方案** |
| 417 | + |
| 418 | + 如果模型的输出分析过于冗余,可以通过提示词控制格式;或者自定义提示词控制指定半结构化格式,例如:csv、json等,下面示例代码: |
| 419 | +```java |
| 420 | +List<Map<String, String>> messages = new ArrayList<>(); |
| 421 | +messages.add(Map.of( |
| 422 | + "role", "system", |
| 423 | + "content": "你是一个SQL优化专家,请按以下格式响应:" |
| 424 | + + "1. 问题描述(纯文本)" |
| 425 | + + "2. 优化建议(无Markdown)" |
| 426 | + // +4.要求内容精简,避免输出过多无用信息 |
| 427 | + // +4.要求输出Markdown格式,便于前端展示 |
| 428 | + // +4.要求输出json格式,便于输入到ES进行分析查询,具体json格式为..." |
| 429 | + + "3. 示例代码(如果适用)" |
| 430 | +)); |
| 431 | +messages.add(Map.of( |
| 432 | + "role", "user", |
| 433 | + "content", "分析SQL: " + sql |
| 434 | +)); |
| 435 | +``` |
165 | 436 |
|
166 | 437 | ## 功能详解 |
167 | 438 |
|
|
0 commit comments