perf(syslog-consumer-rule): 消除达梦批写动态SQL性能瓶颈
- 使用原生JDBC PreparedStatement执行标准化事实批写 - 按批次字段并集只生成一次INSERT语句 - 移除批写主路径逐行执行MyBatis OGNL动态SQL的开销 - 从达梦表元数据缓存合法列名和NULL字段类型 - 保留告警优先、事务一致性及失败隔离逻辑 - 默认关闭统计变更捕获时跳过无效MyBatis BatchSession - 增加SQL规划、参数绑定和批量执行耗时指标 - 补充JDBC批写、字段白名单及参数绑定测试
This commit is contained in:
+125
@@ -0,0 +1,125 @@
|
||||
package com.common.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.Types;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class DmPreparedBatchInserterTest {
|
||||
|
||||
@Test
|
||||
void buildsOneCanonicalInsertFromBatchUnionAndDatabaseWhitelist() {
|
||||
Map<String, Integer> columns = new LinkedHashMap<>();
|
||||
columns.put("id", Types.VARCHAR);
|
||||
columns.put("created_at", Types.TIMESTAMP);
|
||||
columns.put("src_ip", Types.VARCHAR);
|
||||
columns.put("dest_ip", Types.VARCHAR);
|
||||
|
||||
Map<String, Object> first = new LinkedHashMap<>();
|
||||
first.put("src_ip", "10.0.0.1");
|
||||
first.put("id", "row-1");
|
||||
first.put("unknown_rule_field", "ignored");
|
||||
Map<String, Object> second = new LinkedHashMap<>();
|
||||
second.put("id", "row-2");
|
||||
second.put("dest_ip", "10.0.0.2");
|
||||
second.put("src_ip", null);
|
||||
|
||||
DmPreparedBatchInserter.BatchPlan plan = DmPreparedBatchInserter.buildPlan(
|
||||
DmPreparedBatchInserter.NORMAL_TABLE, Arrays.asList(first, second),
|
||||
DmPreparedBatchInserter.TableMetadata.of(columns));
|
||||
|
||||
assertEquals("INSERT INTO syslog_normal_data (id,src_ip,dest_ip) VALUES (?,?,?)", plan.sql);
|
||||
assertEquals(3, plan.columns.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindsDatesAndUsesDatabaseTypeForMissingValues() throws Exception {
|
||||
PreparedStatement statement = mock(PreparedStatement.class);
|
||||
LocalDateTime createdAt = LocalDateTime.of(2026, 9, 13, 2, 30);
|
||||
Date logTime = new Date(1_700_000_000_000L);
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("id", "row-1");
|
||||
row.put("created_at", createdAt);
|
||||
row.put("log_time", logTime);
|
||||
|
||||
List<DmPreparedBatchInserter.ColumnMetadata> columns = Arrays.asList(
|
||||
new DmPreparedBatchInserter.ColumnMetadata("id", Types.VARCHAR),
|
||||
new DmPreparedBatchInserter.ColumnMetadata("created_at", Types.TIMESTAMP),
|
||||
new DmPreparedBatchInserter.ColumnMetadata("log_time", Types.TIMESTAMP),
|
||||
new DmPreparedBatchInserter.ColumnMetadata("dest_ip", Types.VARCHAR));
|
||||
|
||||
DmPreparedBatchInserter.bindRow(statement, row, columns);
|
||||
|
||||
verify(statement).setObject(1, "row-1");
|
||||
verify(statement).setTimestamp(2, java.sql.Timestamp.valueOf(createdAt));
|
||||
verify(statement).setTimestamp(3, new java.sql.Timestamp(logTime.getTime()));
|
||||
verify(statement).setNull(4, Types.VARCHAR);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDynamicTableNames() {
|
||||
Map<String, Integer> columns = new LinkedHashMap<>();
|
||||
columns.put("id", Types.VARCHAR);
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("id", "row-1");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> DmPreparedBatchInserter.buildPlan(
|
||||
"syslog_normal_data;drop table x", Arrays.asList(row),
|
||||
DmPreparedBatchInserter.TableMetadata.of(columns)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void preparesOneStatementAndExecutesOneJdbcBatchForAllRows() throws Exception {
|
||||
Connection connection = mock(Connection.class);
|
||||
PreparedStatement metadataStatement = mock(PreparedStatement.class);
|
||||
ResultSet metadataResult = mock(ResultSet.class);
|
||||
ResultSetMetaData resultSetMetaData = mock(ResultSetMetaData.class);
|
||||
PreparedStatement insertStatement = mock(PreparedStatement.class);
|
||||
when(connection.prepareStatement("SELECT * FROM syslog_normal_data WHERE 1=0"))
|
||||
.thenReturn(metadataStatement);
|
||||
when(metadataStatement.executeQuery()).thenReturn(metadataResult);
|
||||
when(metadataResult.getMetaData()).thenReturn(resultSetMetaData);
|
||||
when(resultSetMetaData.getColumnCount()).thenReturn(2);
|
||||
when(resultSetMetaData.getColumnName(1)).thenReturn("ID");
|
||||
when(resultSetMetaData.getColumnName(2)).thenReturn("SRC_IP");
|
||||
when(resultSetMetaData.getColumnType(1)).thenReturn(Types.VARCHAR);
|
||||
when(resultSetMetaData.getColumnType(2)).thenReturn(Types.VARCHAR);
|
||||
when(connection.prepareStatement(
|
||||
"INSERT INTO syslog_normal_data (id,src_ip) VALUES (?,?)"))
|
||||
.thenReturn(insertStatement);
|
||||
when(insertStatement.executeBatch()).thenReturn(new int[]{1, 1});
|
||||
|
||||
Map<String, Object> first = new LinkedHashMap<>();
|
||||
first.put("id", "row-1");
|
||||
first.put("src_ip", "10.0.0.1");
|
||||
Map<String, Object> second = new LinkedHashMap<>();
|
||||
second.put("id", "row-2");
|
||||
|
||||
DmPreparedBatchInserter inserter = new DmPreparedBatchInserter(mock(
|
||||
org.springframework.jdbc.core.JdbcTemplate.class));
|
||||
DmPreparedBatchInserter.BatchExecution result = inserter.execute(connection,
|
||||
DmPreparedBatchInserter.NORMAL_TABLE, Arrays.asList(first, second), 45);
|
||||
|
||||
assertEquals(2, result.getRows());
|
||||
assertEquals(2, result.getColumns());
|
||||
verify(insertStatement, times(2)).addBatch();
|
||||
verify(insertStatement).executeBatch();
|
||||
verify(insertStatement).setNull(2, Types.VARCHAR);
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
package com.common.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
class DmStandardizedBatchExecutorTest {
|
||||
|
||||
@Test
|
||||
void alignsEveryRowToTheNonNullColumnUnionWithoutMutatingSources() {
|
||||
Map<String, Object> first = new HashMap<>();
|
||||
first.put("id", "row-1");
|
||||
first.put("src_ip", "10.0.0.1");
|
||||
first.put("unused_null", null);
|
||||
|
||||
Map<String, Object> second = new HashMap<>();
|
||||
second.put("id", "row-2");
|
||||
second.put("dest_ip", "10.0.0.2");
|
||||
|
||||
List<Map<String, Object>> aligned =
|
||||
DmStandardizedBatchExecutor.alignBatchColumns(Arrays.asList(first, second));
|
||||
|
||||
assertEquals(aligned.get(0).keySet(), aligned.get(1).keySet());
|
||||
assertEquals(Arrays.asList("dest_ip", "id", "src_ip"),
|
||||
aligned.get(0).keySet().stream().sorted().collect(java.util.stream.Collectors.toList()));
|
||||
assertEquals("10.0.0.1", aligned.get(0).get("src_ip"));
|
||||
assertNull(aligned.get(0).get("dest_ip"));
|
||||
assertNull(aligned.get(1).get("src_ip"));
|
||||
assertEquals("10.0.0.2", aligned.get(1).get("dest_ip"));
|
||||
assertFalse(aligned.get(0).containsKey("unused_null"));
|
||||
assertFalse(first.containsKey("dest_ip"), "不得修改上游标准化结果 Map");
|
||||
assertFalse(second.containsKey("src_ip"), "不得修改上游标准化结果 Map");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user