У меня есть таблицаfund_return с кластерным уникальным индексом по 5 столбцам. Я использую оператор MERGE для обновления существующих записей или вставки новых.
Схема таблицы:
Код: Выделить всё
CREATE TABLE fund_return (
id int IDENTITY(1,1) NOT NULL,
fund_code varchar(8) NOT NULL,
currency varchar(3) NOT NULL,
data_date date NOT NULL,
period varchar(16) NOT NULL,
holiday_type varchar(32) DEFAULT 'NextBusinessDay' NOT NULL,
fund_return numeric(30,15) NULL,
deposit_return_ann numeric(30,15) NULL,
fund_benchmark_return numeric(30,15) NULL,
modif_time datetime NULL,
CONSTRAINT pk_fund_return PRIMARY KEY (id),
CONSTRAINT uc_fund_return_unique UNIQUE CLUSTERED (fund_code, currency, data_date, period, holiday_type)
);
Код: Выделить всё
private static final String UPSERT_SQL = """
MERGE INTO fund_return AS t
USING (VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)) AS s
(fund_code, currency, data_date, period, holiday_type, fund_return, deposit_return_ann, fund_benchmark_return, modif_time)
ON t.fund_code = s.fund_code AND t.currency = s.currency
AND t.data_date = s.data_date AND t.period = s.period AND t.holiday_type = s.holiday_type
WHEN MATCHED THEN UPDATE SET
fund_return = s.fund_return, deposit_return_ann = s.deposit_return_ann,
fund_benchmark_return = s.fund_benchmark_return, modif_time = s.modif_time
WHEN NOT MATCHED THEN INSERT
(fund_code, currency, data_date, period, holiday_type, fund_return, deposit_return_ann, fund_benchmark_return, modif_time)
VALUES (s.fund_code, s.currency, s.data_date, s.period, s.holiday_type, s.fund_return,
s.deposit_return_ann, s.fund_benchmark_return, s.modif_time);
""";
public void batchUpsert(List returns) {
jdbcTemplate.batchUpdate(UPSERT_SQL, returns, 1000, (ps, r) -> {
ps.setString(1, r.getFundCode());
ps.setString(2, r.getCurrency());
ps.setDate(3, java.sql.Date.valueOf(r.getDataDate()));
ps.setString(4, r.getPeriod());
ps.setString(5, r.getHolidayType());
ps.setBigDecimal(6, r.getFundReturn());
ps.setBigDecimal(7, r.getDepositReturnAnn());
ps.setBigDecimal(8, r.getFundBenchmarkReturn());
ps.setTimestamp(9, new Timestamp(System.currentTimeMillis()));
});
}
Код: Выделить всё
log.info("Saving {} returns...", returns.size());
long saveStart = System.currentTimeMillis();
fundReturnService.batchUpsert(returns); // Calls the method above
log.info("Returns saved in {}ms", System.currentTimeMillis() - saveStart);
Код: Выделить всё
DB_URL = jdbc:sqlserver://IP_ADDRESS:PORT;databaseName=dbNAme;trustServerCerftificate=true
(I added sendStringParametersAsUnicode=false but it did not change anything.)
INFO --- c.i.b.m.f.s.FundPriceTransferService : Saving 14088 returns...
INFO --- c.i.b.m.f.s.FundPriceTransferService : Returns saved in 28695ms
...
INFO --- c.i.b.m.f.s.FundPriceTransferService : Saving 22000 returns...
INFO --- c.i.b.m.f.s.FundPriceTransferService : Returns saved in 80000ms
- При обработке около 14 088 записей операция занимает почти 29 секунд (28 695 мс).
- В недавнем запуске отладки с ~22 000 записей процесс занял 80 секунд.
Однако, несмотря на то, что я использую пакетное обновление, оператор MERGE выполняется построчно (я не уверен, что даже эта логика должна занимать такое время) на стороне ядра базы данных, что приводит к линейному увеличению времени.
Как улучшить эту операцию сохранения? Я испробовал все предложения ИИ, но существенных улучшений не добился.
Подробнее здесь: https://stackoverflow.com/questions/798 ... update-for