Различное блокировочное поведение, запускающее запрос из кода с @transactional и работаю на SQL ServerJAVA

Программисты JAVA общаются здесь
Anonymous
Различное блокировочное поведение, запускающее запрос из кода с @transactional и работаю на SQL Server

Сообщение Anonymous »

Я создал следующий тестовый сценарий: < /p>

Код: Выделить всё

CREATE TABLE master.mytest.control_table (
id int IDENTITY(1,1) NOT NULL,
uuid varchar(36) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
status int NOT NULL,
CONSTRAINT PK__control___3213E83F4F29C19D PRIMARY KEY (id)
);
CREATE NONCLUSTERED INDEX IControlTable_Uuid ON master.mytest.control_table (  uuid ASC  )
WITH (  PAD_INDEX = OFF ,FILLFACTOR = 100  ,SORT_IN_TEMPDB = OFF , IGNORE_DUP_KEY = OFF , STATISTICS_NORECOMPUTE = OFF , ONLINE = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON  )
ON [PRIMARY ] ;
< /code>
Таблица содержит 3 столбца: id (pk, автоматическое сгенерирование), Uuid (varchar (36)) и Status (int). Я создал не кластеризованный индекс на uuid.DECLARE @i INT = 1;
WHILE @i 
Моя таблица содержит 4M записи со случайными Uuids. Затем я провел следующие тесты, я запустил запрос «Выбрать» с таблицами подсказки updlock, holdlock и запрашивал замки, сгенерированные в таблице DM_TRAN_LOCKS. Поиск UUID не существует в таблице.SELECT resource_type, request_session_id, resource_associated_entity_id, request_mode, request_type, request_status, COUNT(*) as 'count' FROM sys.dm_tran_locks
GROUP BY resource_type, request_session_id, resource_associated_entity_id, request_mode, request_type, request_status
ORDER BY request_session_id;
< /code>
Первый тестовый пример запустил запрос с задержкой до коммита: < /p>
BEGIN TRANSACTION

SELECT * FROM mytest.control_table WITH(UPDLOCK, HOLDLOCK)
WHERE uuid = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX';

WAITFOR DELAY '00:00:10';

COMMIT TRANSACTION;
Таблица блокировки:

. КЛЮЧ. Другие запросы все еще могут работать нормально, за исключением случаев, когда они также ищут один и тот же UUID, с теми же подсказок запроса. Класс следующим образом: < /p>
@Service
@RequiredArgsConstructor
@Slf4j
public class StatusService {

private final StatusRepository repository;

@Transactional(isolation = Isolation.READ_COMMITTED)
public void getUUID(StatusDomain statusDomain) {
log.info("Thread {} entered get method", currentThread().getName());

StatusDomain current = repository.findByUuidWithHoldLock(statusDomain.getUuid());

log.info("Thread {} selected registry {}", currentThread().getName(), current);
ThreadUtils.sleep(10000); //pauses execution for 10s
log.info("Thread {} exited get method", currentThread().getName());
}
}
< /code>
Класс StatusDomain просто представляет таблицу, имеющая идентификатор, UUID и статус. Только для этого теста. Я использую JDBI для реализации. < /P>
@Repository
@UseClasspathSqlLocator
public interface StatusRepository {

@SqlQuery
@RegisterBeanMapper(StatusDomain.class)
StatusDomain findByUuidWithHoldLock(@Bind("uuid") String uuid);

}
< /code>
Конфигурация JDBI: < /p>
@Configuration
public class JdbiConfiguration {

@Bean
public JdbiPlugin sqlObjectPlugin() {
return new SqlObjectPlugin();
}

@Bean
public Jdbi jdbi(DataSource dataSource, List jdbiPlugins) {
TransactionAwareDataSourceProxy dataSourceProxy = new TransactionAwareDataSourceProxy(dataSource);
Jdbi jdbi = Jdbi.create(dataSourceProxy);

jdbiPlugins.forEach(jdbi::installPlugin);

return jdbi;
}

@Bean
public StatusRepository statusRepository(Jdbi jdbi) {
return jdbi.onDemand(StatusRepository.class);
}
}
< /code>
Запрос findbyuuidwithholdlock определяется как: < /p>
SELECT * FROM mytest.control_table WITH(UPDLOCK, HOLDLOCK)
WHERE uuid = :uuid
< /code>
Цель состоит в том, чтобы просто запросить таблицу замков после выбора и перед выходом на метод, прежде чем совершить транзакцию.@ActiveProfiles(profiles = {"local"})
@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
class StatusServiceTest {

@Autowired
public ApplicationContext applicationContext;

@Autowired
public StatusService service;

@MockitoSpyBean
public StatusRepository repository;

private final StatusDomain status4 = new StatusDomain(55L, "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", 4);

@BeforeEach
void setUp() {
repository.deleteByUuid(status4.getUuid());
}

@Test
void testGet() {
assertDoesNotThrow(() -> {
Thread t1 = new Thread(() -> service.getUUID(status4), "THREAD1");
t1.start();
t1.join();
});

Mockito.verify(repository, times(1)).findByUuidWithHoldLock(anyString());
}
}
< /code>
@beforeeach Удаляет запрос UUID, чтобы убедиться, что он не существует в таблице перед каждым тестом. Репозиторий аннотируется с помощью Mockitospybybean, поэтому я могу сделать утверждение о том, сколько раз его вызывали. < /P>
Запрос таблицы замков во время выполнения теста, я обнаружил это: < /p>
Заблокированная таблица: < /p>

Сгенерированные блокировки отличаются, чем при выполнении непосредственно одного и того же запроса в SQL Server. Теперь я получил IX на объекте, много блокировки u на странице, и без ranges-u блокировки на клавиш, как и раньше. Мои первые тесты выявили x блокировку на столе. Итак, я создал еще одну таблицу, называемую States_Control2, с тем же сценарием DDL и создал индекс, просто переименовав его в iControltable_uuid2, и сделал тест снова. Выполненный запрос такой же, за исключением того, что теперь он запросит status_control2. Разница, однако, это количество записей, вставленных через петлю while. В Status_control2 я положил только 100K записи, в то время как Status_control имеет 4 миллиона. (X) Lock. < /P>
Это уже давно меня беспокоит. Я не могу понять, почему другое поведение. I mean, the different behavior between the locks in status_control and status_control2, and specially the different behavior between running the query directly in SQL Server and running through Java application with JDBI.
SQL Server version:
Microsoft SQL Server 2022 (RTM-CU20-GDR) (KB5063814) 16.0.4210.1 (X64)
11 июля 2025 г. 13:04:02
Copyright (C) 2022 Microsoft Corporation
Developer Edition (64-бит) на Linux (Ubuntu 22.04.5 LTS) < /p>

Подробнее здесь: https://stackoverflow.com/questions/797 ... nsactional

Вернуться в «JAVA»