Наскоро срещнах висок TRANSACTION_MUTEX
натрупано време за изчакване в клиентска система. Не можех да си спомня случай, в който видях този тип на изчакване като най-близо до горната част на списъка с „високо чакане“ и ми беше любопитно какви фактори биха могли да доведат до този тип общо време на чакане.
Дефиницията на Books Online за TRANSACTION_MUTEX
е, че "се случва по време на синхронизиране на достъп до транзакция от множество партиди." Не много области в двигателя на SQL Server разкриват този тип функционалност, така че моето разследване беше стеснено до следните технологии:
- Оттегленият
sp_getbindtoken
иsp_bindsession
системни съхранени процедури, използвани за обработка на свързани връзки - Разпределени транзакции
- MARS (множество активни набори от резултати)
Целта ми беше да тествам всяка технология и да видя дали тя е повлияла на TRANSACTION_MUTEX
тип чакане.
Първият тест, който направих, използва отхвърления sp_getbindtoken
и sp_bindsession
съхранени процедури. sp_getbindtoken
връща идентификатор на транзакция, който след това може да се използва от sp_bindsession
за свързване на няколко сесии заедно за една и съща транзакция.
Преди всеки тестов сценарий се уверих, че изчистих статистиката за чакане на моя тестов екземпляр на SQL Server:
DBCC SQLPERF('waitstats', CLEAR); GO
Моят тестов екземпляр на SQL Server работеше с SQL Server 2012 SP1 Developer Edition (11.0.3000). Използвах примерната база данни Credit, въпреки че бихте могли да използвате всякакъв друг вид примерна база данни като AdventureWorks, ако искате, тъй като схемата и разпределението на данните не са пряко свързани с темата на тази статия и не е необходимо, за да управлявате TRANSACTION_MUTEX
време за изчакване.
sp_getbindtoken / sp_bindsession
В първия прозорец на сесията на SQL Server Management Studio изпълних следния код, за да започна транзакция и да изведа токена за свързване за записване от другите планирани сесии:
USE Credit; GO BEGIN TRANSACTION; DECLARE @out_token varchar(255); EXECUTE sp_getbindtoken @out_token OUTPUT; SELECT @out_token AS out_token; GO
Това върна @out_token
на S/Z5_GOHLaGY<^i]S9LXZ-5---.fE---
. В два отделни прозореца за заявка на SQL Server Management Studio изпълних следния код, за да се присъединя към съществуващите сесии (достъп до споделената транзакция):
USE Credit; GO EXEC sp_bindsession 'S/Z5_GOHLaGY<^i]S9LXZ-5---.fE---';
И тъй като първият прозорец на сесията все още е отворен, започнах следния цикъл, за да актуализирам таблицата на таблицата за таксуване с дата на таксуване, равна на текущата дата и час, и след това изпълних същата логика в другите два прозореца (три активни сесии в цикъл):
WHILE 1 = 1 BEGIN UPDATE dbo.charge SET charge_dt = SYSDATETIME(); END
След няколко секунди отмених всяка изпълняваща се заявка. От трите сесии само една успя да извърши актуализации – въпреки че другите две сесии бяха активно присъединени към същата транзакция. И ако погледнах TRANSACTION_MUTEX
чакам тип, виждам, че наистина се увеличи:
SELECT [wait_type], [waiting_tasks_count], [wait_time_ms], [max_wait_time_ms], [signal_wait_time_ms] FROM sys.dm_os_wait_stats WHERE wait_type = 'TRANSACTION_MUTEX';
Резултатите за този конкретен тест бяха както следва:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 2 181732 93899 0
Така че виждам, че имаше две чакащи задачи (двете сесии, които едновременно се опитваха да актуализират една и съща таблица чрез цикъла). Тъй като не бях изпълнил SET NOCOUNT ON
, успях да видя, че само първото изпълнено UPDATE
цикълът получи промени. Опитах тази подобна техника, използвайки няколко различни варианта (например – четири общи сесии, с три чакащи) – и TRANSACTION_MUTEX
увеличаването показа подобно поведение. Видях и TRANSACTION_MUTEX
натрупване при едновременно актуализиране на различна таблица за всяка сесия – така че не са необходими модификации срещу един и същ обект в цикъл, за да се възпроизведе TRANSACTION_MUTEX
натрупване на време за изчакване.
Разпределени транзакции
Следващият ми тест включваше проверка дали TRANSACTION_MUTEX
времето за изчакване беше увеличено за разпределени транзакции. За този тест използвах два екземпляра на SQL Server и свързан сървър, свързан между тях. MS DTC работеше и беше правилно конфигуриран и аз изпълних следния код, който извърши локално DELETE
и дистанционно DELETE
чрез свързания сървър и след това връща промените:
USE Credit; GO SET XACT_ABORT ON; -- Assumes MS DTC service is available, running, properly configured BEGIN DISTRIBUTED TRANSACTION; DELETE [dbo].[charge] WHERE charge_no = 1; DELETE [JOSEPHSACK-PC\AUGUSTUS].[Credit].[dbo].[charge] WHERE charge_no = 1; ROLLBACK TRANSACTION;
TRANSACTION_MUTEX
не показа активност на локалния сървър:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 0 0 0 0
Броят на чакащите задачи обаче беше увеличен на отдалечения сървър:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 1 0 0 0
Така че очакванията ми да видя това се потвърдиха – като се има предвид, че имаме една разпределена транзакция с повече от една сесия, участващи по някакъв начин с една и съща транзакция.
MARS (множество активни набори от резултати)
Какво ще кажете за използването на множество активни набори от резултати (MARS)? Бихме ли очаквали също да видим TRANSACTION_MUTEX
се натрупват, когато са свързани с използването на MARS?
За това използвах следния код на конзолното приложение на C#, тестван от Microsoft Visual Studio срещу моя екземпляр на SQL Server 2012 и базата данни Credit. Логиката на това, което всъщност правя, не е много полезна (връща един ред от всяка таблица), но четците на данни са на една и съща връзка и атрибутът на връзката MultipleActiveResultSets
е настроен на true, така че беше достатъчно да се провери дали MARS наистина може да управлява TRANSACTION_MUTEX
натрупване също:
string ConnString = @"Server=.;Database=Credit;Trusted_Connection=True;MultipleActiveResultSets=true;"; SqlConnection MARSCon = new SqlConnection(ConnString); MARSCon.Open(); SqlCommand MARSCmd1 = new SqlCommand("SELECT payment_no FROM dbo.payment;", MARSCon); SqlCommand MARSCmd2 = new SqlCommand("SELECT charge_no FROM dbo.charge;", MARSCon); SqlDataReader MARSReader1 = MARSCmd1.ExecuteReader(); SqlDataReader MARSReader2 = MARSCmd2.ExecuteReader(); MARSReader1.Read(); MARSReader2.Read(); Console.WriteLine("\t{0}", MARSReader1[0]); Console.WriteLine("\t{0}", MARSReader2[0]);
След като изпълних този код, видях следното натрупване за TRANSACTION_MUTEX
:
wait_type waiting_tasks_count wait_time_ms max_wait_time_ms signal_wait_time_ms TRANSACTION_MUTEX 8 2 0 0
Както можете да видите, активността на MARS (колкото и тривиално приложена) предизвика нарастване в TRANSACTION_MUTEX
изчакване тип натрупване. И самият атрибут на низ за свързване не управлява това, действителната реализация го прави. Например премахнах втората реализация на четеца и просто поддържах единичен четец с MultipleActiveResultSets=true
, и както се очакваше, нямаше TRANSACTION_MUTEX
натрупване на време за изчакване.
Заключение
Ако виждате високо TRANSACTION_MUTEX
чака във вашата среда, надявам се, че тази публикация ви дава известна представа за три начина, които да проучите – за да определите откъде идват тези изчаквания и дали са необходими или не.