前言
????????最近在做數(shù)據(jù)寫入服務(wù)的性能優(yōu)化,主要是基于Mybatis-Plus實現(xiàn)一套批量寫數(shù)據(jù)的服務(wù),不過該服務(wù)是支持整個平臺所有需要持久化的業(yè)務(wù)實體。所以這種服務(wù)不僅僅有insert操作還有update的操作。根據(jù)以往的MySQL數(shù)據(jù)庫寫入經(jīng)驗,主要總結(jié)了兩套批量插入、批量插入更新的優(yōu)化思路。
應(yīng)用場景
1、純插入、純更新
2、插入+更新共存
可行性分析
在本地環(huán)境使用JMeter驗證MySQL插入性能,以t_xxx表(55個字段)表為例,分別用1個線程、2個線程、10個線程測試寫入Mysql,不同批次大小批量Replace into結(jié)果如下:
批次大小 |
線程數(shù) |
TPS |
|
10/20/50/100/200/500 |
1/2/10 |
空表replace into(全是insert) |
50w數(shù)據(jù)表replace into(全是update) |
10 |
1 |
125*10=1250 |
99*10=990 |
20 |
106*20=2120 |
76*20=1520 |
|
50 |
70*50=3500 |
40*50=2000 |
|
100 |
42*100=4200 |
22*100=2200 |
|
200 |
12*200=2400 |
10*200=2000 |
|
500 |
3.6*500=1800 |
1.2*500=600 |
|
10 |
2 |
210*10=2100 |
190*10=1900 |
20 |
186*20=3820 |
138*20=2760 |
|
50 |
128*50=6400 |
74*50=3700 |
|
100 |
73*100=7300 |
42*100=4200 |
|
200 |
22*200=4400 |
20*200=4000 |
|
500 |
6.7*500=3350 |
6.1*500=3050 |
|
10 |
10 |
1200*10=12000 |
641*10=6410 |
20 |
905*20=18100 |
429*20=8580 |
|
50 |
525*50=26250 |
192*50=9600 |
|
100 |
230*100=23000 |
85*100=8500 |
|
200 |
110*200=22000 |
42*200=8400 |
|
500 |
25*500=12500 |
16*500=8000 |
|
- 無論空表insert、50w表update,最優(yōu)的批次都是在50~100,所以最大500一個批次寫庫是否合理?
- 50w表單線程插入Replace寫入TPS=2000~4200
思路
主要從兩個方面來考慮這個問題:
- SQL本身的執(zhí)行效率
- 網(wǎng)絡(luò)I/O
純插入、純更新
批量插入的時候,一般有兩種思路:
(1)用一個 for 循環(huán),把數(shù)據(jù)一條一條的插入(這種需要開啟批處理)
insert into t_xx(a) values(1);
insert into t_xx(a) values(2);
開啟批處理簡單的講就是openSession的時候帶上參數(shù)ExecutorType.BATCH
,可以幾乎無損優(yōu)化你的代碼性能。
SqlSession session = sessionFactory.openSession(ExecutorType.BATCH);
(2)生成一條插入 sql,類似這種
insert into t_xx(a) values(1),(2);
- 這種方案的優(yōu)勢在于只有一次網(wǎng)絡(luò) IO,即使分片處理也只是數(shù)次網(wǎng)絡(luò) IO,所以這種方案不會在網(wǎng)絡(luò) IO 上花費太多時間。
- 當(dāng)然這種方案有好幾個劣勢,一是 SQL 太長了,甚至可能需要分片后批量處理;二是無法充分發(fā)揮 PreparedStatement 預(yù)編譯的優(yōu)勢,SQL 要重新解析且無法復(fù)用;三是最終生成的 SQL 如果太長了,數(shù)據(jù)庫管理器解析這么長的 SQL 也需要時間。
插入+更新共存
批量插入和更新,一般采用
(1)insert、update語句分開批量處理
第一種這種和上述純插入、純更新處理類似,但是一般這種處理比較麻煩的點在于如何分類。一般對于區(qū)分可能需要根據(jù)UK查詢一把。
(2)使用replace into語句批量處理
replace into t_xx(a) values(1),(2);
????????這種方案主要的原理是delete + insert,這種如果在多線程下執(zhí)行出現(xiàn)死鎖的概率很大。
目前基于mybatis-plus自定義SQL注入器方法實現(xiàn),主要代碼如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-857907.html
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
KeyGenerator keyGenerator = new NoKeyGenerator();
String scriptSql = "<script>\n%s\nVALUES %s\n</script>";
String replaceSql = "REPLACE INTO %s %s";
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(false) +
this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(ENTITY_DOT, false) +
this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
String keyProperty = null;
String keyColumn = null;
if (tableInfo.havePK()) {
if (tableInfo.getIdType() == IdType.AUTO) {
keyGenerator = new NoKeyGenerator();
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
} else {
if (null != tableInfo.getKeySequence()) {
keyGenerator = TableInfoHelper.genKeyGenerator(functionName, tableInfo, builderAssistant);
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
}
}
}
String sql = String.format(scriptSql, String.format(replaceSql, tableInfo.getTableName(), columnScript), valuesScript);
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass, functionName, sqlSource, keyGenerator, keyProperty, keyColumn);
}
(3)使用insert into tb values() on duplicate key update批量處理
insert into t_xx(a) values(1),(2) on duplicate key update a=values(a)
? ? ? ? 這種方案主要的原理是根據(jù)uk是否沖突判斷,是否執(zhí)行insert或者update,這種如果在多線程下執(zhí)行會出現(xiàn)死鎖,但是沖突概率相比較方案2要小很多。文章來源:http://www.zghlxwxcb.cn/news/detail-857907.html
目前基于mybatis-plus自定義SQL注入器方法實現(xiàn),主要代碼如下:
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
KeyGenerator keyGenerator = new NoKeyGenerator();
String scriptSql = "<script>\n%s\nVALUES %s on duplicate key update %s\n</script>";
String insertSql = "INSERT INTO %s %s";
String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(false) +
this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
String updateSqlColumn = getUpdateSqlProperties(insertSqlColumn);
String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(ENTITY_DOT, false) +
this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
String keyProperty = null;
String keyColumn = null;
if (tableInfo.havePK()) {
if (tableInfo.getIdType() == IdType.AUTO) {
keyGenerator = new NoKeyGenerator();
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
} else {
if (null != tableInfo.getKeySequence()) {
keyGenerator = TableInfoHelper.genKeyGenerator(functionName, tableInfo, builderAssistant);
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
}
}
}
String sql = String.format(scriptSql, String.format(insertSql, tableInfo.getTableName(), columnScript), valuesScript, updateSqlColumn);
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass, functionName, sqlSource, keyGenerator, keyProperty, keyColumn);
}
到了這里,關(guān)于Mybatis批量插入/更新性能優(yōu)化思路的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!