Различное блокировочное поведение, запускающее запрос из кода с @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;
План запроса: https://www.brentozar.com/pastetheplan/?id=32occcc4kvli
Блокирует таблицу:

Как мы видим, мы получили блокировку IX на объекте, блокировку IU на странице и диапазоны-U на ключе. Другие запросы все еще могут работать нормально, за исключением случаев, когда они также ищут один и тот же 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, чтобы убедиться, что он не существует в таблице перед каждым тестом. Репозиторий аннотируется с помощью Mockitospybean, поэтому я могу сделать утверждение о том, сколько раз его вызывали. https://www.brentozar.com/pastetheplan/?id=pcpxa0cpeo
Таблица блокировки:


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

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

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