Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a39202a
fix: make some fields final
brenoepics May 1, 2024
8d1358b
fix: unnecessary 'toString()' call
brenoepics May 1, 2024
bb487e7
fix: remove redundant modifier
brenoepics May 1, 2024
1bdf6a1
fix: remove unnecessary semicolon
brenoepics May 1, 2024
3c26616
fix: remove unnecessary semicolon
brenoepics May 1, 2024
8621b28
fix: raw use of parameterized class
brenoepics May 1, 2024
e1e16c8
fix: raw use
brenoepics May 1, 2024
f6d756e
fix: redundant if statement
brenoepics May 1, 2024
c0cd2bd
fix: redundant local variable
brenoepics May 1, 2024
916547c
fix: this can be final
brenoepics May 1, 2024
10504fa
fix: raw use
brenoepics May 1, 2024
88de688
fix: explicit types can be replaced
brenoepics May 1, 2024
9670258
fix: this can be replaced with lambda
brenoepics May 1, 2024
26868f0
fix: lambda method reference
brenoepics May 1, 2024
1ef4bf4
fix: replace concatenation to parameterized log
brenoepics May 1, 2024
3c30b1a
fix: long literal
brenoepics May 1, 2024
3d65fa0
perf: type may be primitive
brenoepics May 1, 2024
c907e0f
fix: remove code duplicate
brenoepics May 1, 2024
215e2b6
fix: inputStream is nullable here
brenoepics May 1, 2024
9e0772f
fix: lombok can be used here
brenoepics May 1, 2024
29447ba
chore: md format cleanup
brenoepics May 1, 2024
f340a2e
fix: enum final vars
brenoepics May 1, 2024
9bd5e6b
fix: this can be simplified
brenoepics May 1, 2024
bf07b49
fix: deprecated
brenoepics May 1, 2024
c86c23d
fix: var
brenoepics May 1, 2024
d2db0ba
fix: lombok setters
brenoepics May 1, 2024
ef2f688
chore: reformat
brenoepics May 1, 2024
3c3bf93
refactor: sample
brenoepics May 1, 2024
3d75351
tests: add some new tests
brenoepics May 1, 2024
fb2012e
chore: readme cleanup
brenoepics May 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 23 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ Mybatis-SQL分析组件

**2、线上出现慢sql后,无法快速止损**



# 解决思路

**1、把问题解决在上线之前,最好的办法就是在测试阶段,甚至在开发阶段就发现一个sql的好坏**
Expand All @@ -20,26 +18,19 @@ Mybatis-SQL分析组件

部门内部,目前大部分数据库框架采用的mybatis,然后基于mybatis本身的实现机制中,开发一个mybatis组件,可以自动对运行的sql进行提取和分析,定制一套默认的分析规则,让sql在开发环境和测试环境执行的时候,就能够做初步的评估,把有问题的慢sql在这个阶段暴露出来;同时具备sql替换功能,在线上出现问题sql的时候,可以通过ducc配置快速完成对一个sql的在线替换,大大降低线上问题的止损时间。



# 开源方案调研

目前,主流的sql分析组件,核心功能主要放在了两个方向:1、慢sql的分析和优化建议 2、sql的优化重写功能,而且主要偏运维的辅助功能无法做到无侵入的和应用代码进行集成。也就无法实现我们的核心痛点,慢sql提前分析预警和动态sql替换。
目前,主流的sql分析组件,核心功能主要放在了两个方向:1、慢sql的分析和优化建议
2、sql的优化重写功能,而且主要偏运维的辅助功能无法做到无侵入的和应用代码进行集成。也就无法实现我们的核心痛点,慢sql提前分析预警和动态sql替换。

![img](https://github.com/huht123/sql-analysis-img/blob/main/%E5%AF%B9%E6%AF%94%E5%9B%BE.png)



# 设计方案

**核心功能:SQL分析预警能力、SQL替换能力**



![img](https://github.com/huht123/sql-analysis-img/blob/main/%E8%AE%BE%E8%AE%A1%E5%9B%BE.png)



# 详细设计

主要分为8个功能模块
Expand Down Expand Up @@ -74,9 +65,7 @@ Mybatis-SQL分析组件

## 1、引入依赖jar包



```
```xml
<dependency>
<groupId>io.github.huht123.sql-analysis</groupId>
<artifactId>sql-analysis</artifactId>
Expand All @@ -86,7 +75,7 @@ Mybatis-SQL分析组件

## 2、配置组件xml

```
```xml
<configuration>
<plugins>
<plugin interceptor="com.jd.sql.analysis.core.SqlAnalysisAspect" >
Expand All @@ -100,28 +89,28 @@ Mybatis-SQL分析组件
</plugins>
</configuration>
```

注意:如果使用了多个mybatis组件,建议把该组件放在最前面,防止其它组件对mybatis相关对象进行二次包装,无法获取对应的数据

## 3、核心配置项

| 属性 | 用途 | 是否必填 | 默认值 | 备注 |
| --------------------- | ------------------------------------------ | -------- | ------------------ | --------------------------- |
| analysisSwitch | 是否开启分析功能 | 是 | false | |
| onlyCheckOnce | 是否对一个sqlid只分析一次 | 非 | true | |
| checkInterval | 每个sqlid分析间隔 | 非 | 300000毫秒 | onlyCheckOnce 为false才生效 |
| exceptSqlIds | 需要过滤不分析的sqlid | 非 | | |
| sqlType | 分析的sql类型 | 非 | 默认select、update | 支持 |
| scoreRuleLoadClass | 评分规则加载器,用于扩展自定义规则 | 非 | | |
| outModel | 默认输出方式 | 非 | 默认值:LOG | 支持LOG、MQ两种方式 |
| outputClass | 评分结果输出类,用于扩展自定义结果输出方式 | 非 | | |
| sqlReplaceModelSwitch | sql替换模块是否开启 | 非 | 默认 false | |

| 属性 | 用途 | 是否必填 | 默认值 | 备注 |
|-----------------------|-----------------------|------|-----------------|-------------------------|
| analysisSwitch | 是否开启分析功能 | 是 | false | |
| onlyCheckOnce | 是否对一个sqlid只分析一次 | 非 | true | |
| checkInterval | 每个sqlid分析间隔 | 非 | 300000毫秒 | onlyCheckOnce 为false才生效 |
| exceptSqlIds | 需要过滤不分析的sqlid | 非 | | |
| sqlType | 分析的sql类型 | 非 | 默认select、update | 支持 |
| scoreRuleLoadClass | 评分规则加载器,用于扩展自定义规则 | 非 | | |
| outModel | 默认输出方式 | 非 | 默认值:LOG | 支持LOG、MQ两种方式 |
| outputClass | 评分结果输出类,用于扩展自定义结果输出方式 | 非 | | |
| sqlReplaceModelSwitch | sql替换模块是否开启 | 非 | 默认 false | |

## 4、实践使用方案

### 1、慢sql分析-日志输出+关键词告警

```
```xml
<configuration>
<plugins>
<plugin interceptor="com.jd.sql.analysis.core.SqlAnalysisAspect" >
Expand All @@ -131,10 +120,9 @@ Mybatis-SQL分析组件
</configuration>
```


### 2、慢sql分析-日志输出+mq输出+es存储+Kibana分析

```
```xml
<configuration>
<plugins>
<plugin interceptor="com.jd.sql.analysis.core.SqlAnalysisAspect" >
Expand All @@ -144,14 +132,14 @@ Mybatis-SQL分析组件
</plugins>
</configuration>
```

实现该接口,自定义输出方式(需要自己保证输出性能,可以采用异步队列)

com.jd.sql.analysis.out.SqlScoreResultOutService


### 3、慢sql替换-配置动态更新sql语句

```
```xml
<configuration>
<plugins>
<plugin interceptor="com.jd.sql.analysis.core.SqlAnalysisAspect" >
Expand All @@ -160,14 +148,13 @@ Mybatis-SQL分析组件
</plugins>
</configuration>
```

可以集成自己环境的配置中心,通过如下方法或者映射map动态更新

com.jd.sql.analysis.replace.SqlReplaceConfig.getSqlReplaceMap

注意:功能正式修复后,需去掉该配置,该功能仅供应急处理线上问题,不建议作为功能长期使用



# 性能测试

测试环境千次普通sql查询,每种场景进行了5次测试
Expand All @@ -182,16 +169,12 @@ Mybatis-SQL分析组件

基本无影响



# 适用场景

1、慢sql预防

2、线上问题止损



# 优势

1、核心优势:执行时分析sql,区别于传统的依赖sql执行耗时来评估慢sql,直接基于语法和索引进行前置分析,不仅能预防某些坏sql在上线后发现是慢sql,还能给出sql优化建议,可以大限度的避免线上产生慢sql。支持动态对线上sql进行替换,可以对线上问题快速止损。
Expand All @@ -202,10 +185,10 @@ Mybatis-SQL分析组件

4、成本:接入成本低,无代码侵入。


# 主要贡献者:

扈海涛(huhaitao21@jd.com)、杨超(yangchao341@jd.com)、张泽龙(zhangzelong10@jd.com)

# 欢迎共同改进和使用咨询
扈海涛(huhaitao21@jd.com)

扈海涛(huhaitao21@jd.com)
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
public interface TaskMapper {
int deleteByPrimaryKey(Long id);

int insert(Task record);
int insert(Task task);

Task selectByPrimaryKey(Long id);

int updateByPrimaryKey(Task record);
int updateByPrimaryKey(Task task);

/**
* 通过实体作为筛选条件查询
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.jd.sql.analysis.util.GsonUtil;
import com.jd.sql.analysis_samples.mapper.TaskMapper;
import com.jd.sql.analysis_samples.po.Task;
import lombok.Getter;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
Expand All @@ -18,74 +19,79 @@
* @Date 14:35 2022/11/11
**/
public class MyBatisUtil {
public static SqlSessionFactory sqlSessionFactory;
@Getter
private static SqlSessionFactory sqlSessionFactory;

static {
InputStream fis = null;
InputStream inputStream = null;
try {
//创建Properties对象
Properties prop = new Properties();
//创建输入流,指向配置文件,getResourceAsStream可以从classpath加载资源
fis= Resources.getResourceAsStream("jdbc.properties");
//加载属性文件
prop.load(fis);
static {
InputStream fis = null;
InputStream inputStream = null;
try {
//创建Properties对象
Properties prop = new Properties();
//创建输入流,指向配置文件,getResourceAsStream可以从classpath加载资源
fis = Resources.getResourceAsStream("jdbc.properties");
//加载属性文件
prop.load(fis);

// 从类路径下加载资源文件mybatis-config.xml
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
// 由 SqlSessionFactoryBuilder创建SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is,"dev",prop);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
// 从类路径下加载资源文件mybatis-config.xml
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
// 由 SqlSessionFactoryBuilder创建SqlSessionFactory
setSqlSessionFactory(new SqlSessionFactoryBuilder().build(is, "dev", prop));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

public static void main(String[]args){
SqlSession sqlSession = getSqlSession();
System.out.println(sqlSession);
TaskMapper mapper = sqlSession.getMapper(TaskMapper.class);
public static void main(String[] args) {
SqlSession sqlSession = getSqlSession();
System.out.println(sqlSession);
TaskMapper mapper = sqlSession.getMapper(TaskMapper.class);

Long id = 21l;
Task task = mapper.selectByPrimaryKey(id);
System.out.println(GsonUtil.bean2Json(task));
}
Long id = 21L;
Task task = mapper.selectByPrimaryKey(id);
System.out.println(GsonUtil.bean2Json(task));
}


/**
* 由 SqlSessionFactory创建SqlSession
*
* @return
*/
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
/**
* 由 SqlSessionFactory创建SqlSession
*
* @return
*/
public static SqlSession getSqlSession() {
return getSqlSessionFactory().openSession();
}

/**
* 关闭SqlSession
*
* @param sqlSession
*/
public static void closeSqlSession(SqlSession sqlSession) {
if (sqlSession != null) {
sqlSession.close();
}
}
/**
* 关闭SqlSession
*
* @param sqlSession
*/
public static void closeSqlSession(SqlSession sqlSession) {
if (sqlSession != null) {
sqlSession.close();
}
}

public static void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
MyBatisUtil.sqlSessionFactory = sqlSessionFactory;
}
}
Loading