Приходилось ли использовать многопоточность для запросов к базе данных
Что проверяют
Понимаете ли вы, где на самом деле находится узкое место при работе с базой, и знаете ли, что в Java параллельные запросы упираются не в число потоков, а в пул соединений и в саму базу. Заодно смотрят, не сломаете ли вы транзакцию, растащив её по потокам.
Как отвечать
Скажите, для чего именно параллелили: обычно это несколько независимых выборок для одного экрана или отчёта, которые не зависят друг от друга. Дальше по делу:
- потоки берутся из своего
ExecutorServiceс понятным размером, а не изForkJoinPool.commonPool, куда по умолчанию уходитCompletableFuture.supplyAsync. Общий пул рассчитан на счётные задачи, и блокирующий JDBC его затыкает; - размер пула потоков согласован с размером пула соединений. Потоков больше, чем соединений, значит лишние просто стоят на
getConnection(); - соединение JDBC не потокобезопасно: одно соединение на поток, возврат в пул через try-with-resources;
@Transactionalв Spring живёт вThreadLocal. Соединение привязано к потоку, и задача, отправленная в executor, транзакцию родителя не видит: это отдельная транзакция со своим коммитом.
Полезно добавить, что часть задач решается без потоков вовсе: пакетная вставка, один запрос с соединением таблиц вместо серии, условие IN вместо цикла.
Где спотыкаются
Считают, что чем больше потоков, тем быстрее. База это общий ресурс: параллельные запросы конкурируют за диск, за блокировки строк и за собственные рабочие процессы, и после некоторого предела растёт только время ожидания.
Тащат @Transactional через границу потока и удивляются, что откат не откатывает вложенные задачи. Атомарности между потоками так не получить, нужна одна транзакция в одном потоке либо явная компенсация.
Пробуют защитить данные через synchronized в приложении. Это не работает: инстансов сервиса несколько, а блокировка живёт внутри одной JVM. Целостность обеспечивают транзакция, SELECT ... FOR UPDATE или версия строки при оптимистической блокировке.