Подтвердить что ты не робот

Почему этот запрос замедляется в первый раз после запуска службы?

Ok. Вот что я пытаюсь запустить:

USE tempdb;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;

Это одна из тех запросов "сделайте меня числами".

Вот проблема. Если я запустил это сразу после запуска службы SQL Server (re), это займет много времени. Не навсегда, как через десять секунд, и я хочу быстрее. Навсегда, как в, я позволил ему пройти два часа один раз случайно и все равно должен был убить его. Я думаю, что он никогда не возвращается. И обычно для моей работы требуется менее двух секунд на моей машине.

Однако, если я это сделаю:

USE tempdb;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3;

DROP TABLE Numbers;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;

Затем он работает так, как вы ожидали, - первый SELECT работает менее чем за две секунды, как и второй. Почему бы мне просто не использовать трехстоечную версию? Поскольку в sys.objects не хватает записей для этого числа с кубиками, равным миллиону строк результата. Но это даже не точка.

В любом случае, отсюда, я могу повторить второй DROP/SELECT…INTO столько, сколько захочу, никаких проблем. Каким-то образом эта первая версия с тремя столами сделала это навсегда. По крайней мере, до следующего раза перезагрузка службы и/или перезагрузка компьютера. В этот момент запуск последнего SELECT снова не возвращается. Опять же.

Здесь, где он начинает становиться еще более странным. Если я переняю первую SELECT обратно в двухэтажную версию:

USE tempdb;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2;

DROP TABLE Numbers;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;

Это также заставляет второй SELECT работать вечно. Как и одна таблица. Так или иначе, эта трехстоловая версия волшебна!

Что здесь происходит? Почему это медленно?

(И прежде, чем кто-нибудь отметит, что я создаю постоянную таблицу в tempdb, да, я знаю. Изменение на фактические временные таблицы не имеет никакого значения.)


Добавлена ​​информация:

  • Это версия для разработчиков SQL Server 2012
  • Вывод EXEC sp_WhoIsActive @find_block_leaders = 1, @sort_order = '[blocked_session_count] DESC' (написанный как XML, поэтому его можно прочитать здесь):
<?xml version="1.0" ?>
<RESULTS1>
    <RECORD>
        <dd hh:mm:ss.mss>00 00:10:45.066</dd hh:mm:ss.mss>
        <session_id>52</session_id>
        <sql_text>&lt;?query --
SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;

--?&gt;</sql_text>
        <login_name>my own login name redacted</login_name>
        <wait_info>(99ms)LCK_M_X</wait_info>
        <CPU>              9,750</CPU>
        <tempdb_allocations>                713</tempdb_allocations>
        <tempdb_current>                702</tempdb_current>
        <blocking_session_id>NULL</blocking_session_id>
        <blocked_session_count>                  0</blocked_session_count>
        <reads>            583,273</reads>
        <writes>                537</writes>
        <physical_reads>                 50</physical_reads>
        <used_memory>                  3</used_memory>
        <status>suspended</status>
        <open_tran_count>                  2</open_tran_count>
        <percent_complete>NULL</percent_complete>
        <host_name>my own machine name redacted</host_name>
        <database_name>tempdb</database_name>
        <program_name>Microsoft SQL Server Management Studio - Query</program_name>
        <start_time>2013-11-23 23:48:19.473</start_time>
        <login_time>2013-11-23 23:47:47.060</login_time>
        <request_id>0</request_id>
        <collection_time>2013-11-23 23:59:04.560</collection_time>
    </RECORD>
</RESULTS1>

Дополнительная информация:

Почему я помещаю это в tempdb, так это то, что он является частью script, предназначенным для запуска в девственных установках, и там гарантируется tempdb. Как я уже сказал, переход на глобальные таблицы temp не отличается.

4b9b3361

Ответ 1

I может также воспроизвести это 100% времени на моей машине. (см. примечание в конце)

Суть проблемы заключается в том, что вы удаляете блокировки S в строках системной таблицы в tempdb, которые могут конфликтовать с блокировками, необходимыми для внутренних транзакций очистки tempdb.

Когда эта работа по очистке распределяется на тот же сеанс, которому принадлежит блокировка S, может возникнуть неопределенное зависание.

Чтобы избежать этой проблемы, вам нужно прекратить ссылаться на объекты system внутри tempdb.

Можно создать таблицу чисел без ссылки на любые внешние таблицы. Следующее должно читать строки базовых таблиц и, следовательно, также не принимать блокировок.

WITH Ten(N) AS 
(
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)   
SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO   Numbers
FROM   Ten T10,
       Ten T100,
       Ten T1000,
       Ten T10000,
       Ten T100000,
       Ten T1000000 

Шаги по воспроизведению

Сначала создайте процедуру

CREATE PROC P
AS
    SET NOCOUNT ON;

    DECLARE @T TABLE (X INT)
GO

Затем перезапустите службу SQL и в одном соединении выполните

WHILE NOT EXISTS(SELECT *
                 FROM   sys.dm_os_waiting_tasks
                 WHERE  session_id = blocking_session_id)
  BEGIN

      /*This will cause the problematic droptemp transactions*/
      EXEC sp_recompile 'P'

      EXEC P
  END;

SELECT *
FROM   sys.dm_os_waiting_tasks
WHERE  session_id = blocking_session_id 

Затем в другом соединении запустите

USE tempdb;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO #T
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;

DROP TABLE #T

Кажется, что запрос, заполняющий таблицу Numbers, попадает в ситуацию блокировки в реальном времени с внутренними системными транзакциями, которые очищают временные объекты, такие как переменные таблицы.

Мне удалось заблокировать идентификатор сеанса 53 таким образом. Он заблокирован на неопределенный срок. Выходной сигнал sp_WhoIsActive показывает, что этот spid тратит почти все время приостановления. В последовательных прогонах числа в столбце reads возрастают, но значения в других столбцах остаются в основном одинаковыми.

Продолжительность ожидания не показывает возрастающий шаблон, хотя указывает, что он должен быть разблокирован периодически, прежде чем снова заблокировать его.

SELECT *
FROM   sys.dm_os_waiting_tasks
WHERE  session_id = blocking_session_id

Возвращает

+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| waiting_task_address | session_id | exec_context_id | wait_duration_ms | wait_type |  resource_address  | blocking_task_address | blocking_session_id | blocking_exec_context_id |                                       resource_description                                       |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| 0x00000002F2C170C8   |         53 |               0 |               86 | LCK_M_X   | 0x00000002F9B13040 | 0x00000002F2C170C8    |                  53 | NULL                     | keylock hobtid=281474978938880 dbid=2 id=lock2f9ac8880 mode=U associatedObjectId=281474978938880 |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+

Использование идентификатора в описании ресурса

SELECT o.name
FROM   sys.allocation_units au WITH (NOLOCK)
       INNER JOIN sys.partitions p WITH (NOLOCK)
         ON au.container_id = p.partition_id
       INNER JOIN sys.all_objects o WITH (NOLOCK)
         ON o.object_id = p.object_id
WHERE  allocation_unit_id = 281474978938880 

Возвращает

+------------+
|    name    |
+------------+
| sysschobjs |
+------------+

Запуск

SELECT resource_description,request_status
FROM   sys.dm_tran_locks 
WHERE request_session_id = 53 AND request_status <> 'GRANT'

Возвращает

+----------------------+----------------+
| resource_description | request_status |
+----------------------+----------------+
| (246708db8c1f)       | CONVERT        |
+----------------------+----------------+

Подключение через ЦАП и запуск

SELECT id,name
FROM   tempdb.sys.sysschobjs WITH (NOLOCK)
WHERE %%LOCKRES%% = '(246708db8c1f)' 

Возвращает

+-------------+-----------+
|     id      |   name    |
+-------------+-----------+
| -1578606288 | #A1E86130 |
+-------------+-----------+

Интересно, что это за

SELECT name,user_type_id
FROM tempdb.sys.columns
WHERE object_id = -1578606288 

Возвращает

+------+--------------+
| name | user_type_id |
+------+--------------+
| X    |           56 |
+------+--------------+

Это имя столбца в переменной таблицы, используемой хранимой процедурой.

Запуск

SELECT request_mode,
       request_status,
       request_session_id,
       request_owner_id,
       lock_owner_address,
       t.transaction_id,
       t.name,
       t.transaction_begin_time
FROM   sys.dm_tran_locks l
       JOIN sys.dm_tran_active_transactions t
         ON l.request_owner_id = t.transaction_id
WHERE  resource_description = '(246708db8c1f)' 

Возвращает

+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| request_mode | request_status | request_session_id | request_owner_id | lock_owner_address | transaction_id |    name     | transaction_begin_time  |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| U            | GRANT          |                 53 |           227647 | 0x00000002F1EF6800 |         227647 | droptemp    | 2013-11-24 18:36:28.267 |
| S            | GRANT          |                 53 |           191790 | 0x00000002F9B16380 |         191790 | SELECT INTO | 2013-11-24 18:21:30.083 |
| X            | CONVERT        |                 53 |           227647 | 0x00000002F9B12FC0 |         227647 | droptemp    | 2013-11-24 18:36:28.267 |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+

Таким образом, транзакция SELECT INTO содержит блокировку S в строке в tempdb.sys.sysschobjs, относящуюся к переменной таблицы #A1E86130. Операция droptemp не может получить блокировку X в этой строке из-за этой конфликтующей блокировки S.

Выполнение этого запроса неоднократно показывает, что транзакция transaction_id для транзакции droptemp неоднократно изменяется.

Я предполагаю, что SQL Server должен распределять эти внутренние транзакции на пользовательских spids и приоритизировать их перед тем, как пользователь будет работать. Таким образом, идентификатор сеанса 53 застрял в постоянном цикле, где он запускает транзакцию droptemp, блокируется транзакцией пользователя, выполняемой на том же spid. Возвращает внутреннюю транзакцию, а затем повторяет процесс на неопределенный срок.

Это подтверждается отслеживанием различных событий блокировки и транзакций в SQL Server Profiler после того, как spid будет виден.

Profiler

Я также отслеживал события блокировки до этого.

Блокировка блокировки событий

LockAquisitionPatternBlockingTransaction

Большинство блокировок разделяемых ключей, извлеченных транзакцией SELECT INTO на клавишах в sysschobjs, немедленно освобождаются. Исключением является первая блокировка на (246708db8c1f).

Это имеет смысл, так как на плане показаны вложенные циклы сканирования [sys].[sysschobjs].[clst] [o] и, поскольку временные объекты получают заданные отрицательные объекты, они будут первыми строками, встречающимися в порядке сканирования.

Я также столкнулся с ситуацией, описанной в OP, где сначала выполняется трехстороннее перекрестное соединение, что позволяет добиться успеха в четырех направлениях.

Первые несколько событий в трассировке для транзакции SELECT INTO имеют совершенно другой шаблон.

LockAquisitionPatternNonBlockingTransaction

Это было после перезапуска службы, поэтому значения ресурса блокировки в столбце текстовых данных не сопоставимы напрямую.

Вместо того, чтобы удерживать блокировку на первом ключе, а затем образец получения и отпускания последующих клавиш, похоже, он приобретает гораздо больше блокировок, не отпуская их изначально.

Я предполагаю, что в стратегии исполнения должна быть некоторая дисперсия, которая позволяет избежать проблемы.


Обновить

Подключенный элемент, о котором я рассказал об этомне был отмечен как фиксированный, но теперь я на SQL Server 2012 с пакетом обновления 2 (SP2) и теперь могу воспроизводить только временную блокировку, а не постоянную. Я по-прежнему получаю самоблокировку, но после некоторого количества неудачных попыток выполнить транзакцию droptemp она, похоже, возвращается к обработке транзакции пользователя. После этого транзакция системы выполняется успешно. Тем не менее на том же спиде. (восемь попыток в одном примере запускаются. Я не уверен, будет ли это повторяться последовательно)

Ответ 2

Вместо того, чтобы преследовать эту проблему, почему бы вам просто не создать таблицу один раз в базе данных model, то она будет создана для вас в tempdb автоматически?

Для реальной проблемы мы не знаем. Мое первое предположение заключалось в том, что ваш первоначальный размер для вашего файла tempdb очень мал (например, 1 МБ). Поэтому, когда вы создаете таблицу, она должна расширять файл для его размещения. Это может быть довольно дорого, особенно если у вас нет мгновенной инициализации файла, и также может быть очень дорого расти журнал для учета требуемой там активности.

Кроме этого, мы могли бы угадать, но вам лучше подойти, чтобы выяснить, что на самом деле происходит. Вопросы, которые вы хотите задать:

  • Для spid, который пытается создать таблицу, что говорит sys.dm_exec_requests для wait_type?
  • Есть ли у него blocking_session_id?
  • Если да, то чем занимается этот сеанс?