У меня есть данные о взаимоблокировках, но я не могу понять, почему они возникают - PullRequest
5 голосов
/ 31 мая 2010

Я получаю много тупиков в моем большом веб-приложении.

Как автоматически перезапустить заблокированную транзакцию? (ASP.NET MVC / SQL Server)

Здесь я хотел повторно запустить заблокированные транзакции, но мне сказали избавиться от тупиков - это гораздо лучше, чем пытаться перехватить тупики.

Итак, я провел весь день с SQL Profiler, устанавливая ключи трассировки и т. Д. И это то, что я получил.

Есть таблица Users. У меня очень полезная страница со следующим запросом (это не единственный запрос, но он вызывает проблемы)

UPDATE Users
SET views = views + 1
WHERE ID IN (SELECT AuthorID FROM Articles WHERE ArticleID = @ArticleID)

И еще есть следующий запрос на ALL страницах:

User = DB.Users.SingleOrDefault(u => u.Password == password && u.Name == username);

Вот где я получаю пользователя от файлов cookie.

Очень часто возникает тупик, и второй запрос Linq-to-SQL выбирается в качестве жертвы, поэтому он не запускается, и пользователи моего сайта видят экран с ошибкой.

Это информация из графика .XDL, захваченного SQL Profiler (это всего лишь первый тупик, и не единственный. Весь список гигантский.):

<deadlock-list>
    <deadlock victim="process824df048">
        <process-list>
            <process id="process824df048" taskpriority="0" logused="0" waitresource="PAGE: 7:1:13921" waittime="1830" ownerId="91418" transactionname="SELECT" lasttranstarted="2010-05-31T12:17:37.663" XDES="0x868175e0" lockMode="S" schedulerid="2" kpid="5076" status="suspended" spid="72" sbid="0" ecid="2" priority="0" trancount="0" lastbatchstarted="2010-05-31T12:17:37.663" lastbatchcompleted="2010-05-31T12:17:37.663" clientapp=".Net SqlClient Data Provider" hostname="WIN-S41KV2CLS67" hostpid="6920" isolationlevel="read committed (2)" xactid="91418" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
                <executionStack>
                    <frame procname="adhoc" line="1" stmtstart="74" sqlhandle="0x02000000de1cb30b5b2e40e31ffb345af3c7529430b559c2">
*password-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------     </frame>
                    <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
                </executionStack>
                <inputbuf>
                </inputbuf>
            </process>
            <process id="process8765fb88" taskpriority="0" logused="216" waitresource="PAGE: 7:1:14196" waittime="1822" ownerId="91408" transactionname="UPDATE" lasttranstarted="2010-05-31T12:17:37.640" XDES="0x86978e90" lockMode="IX" schedulerid="2" kpid="5216" status="suspended" spid="73" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2010-05-31T12:17:37.557" lastbatchcompleted="2010-05-31T12:17:37.557" clientapp=".Net SqlClient Data Provider" hostname="WIN-S41KV2CLS67" hostpid="6920" loginname="sdfkj93jks9sl" isolationlevel="read committed (2)" xactid="91408" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
                <executionStack>
                    <frame procname="database.dbo.UpdateUserStats" line="31" stmtstart="1794" stmtend="2088" sqlhandle="0x03000700bac8836333e58f00879d00000100000000000000">
UPDATE Users
    SET Views = Views + 1
    WHERE ID IN (SELECT AuthorID FROM Articles WHERE ArticleID = @ArticleID)     </frame>
                    <frame procname="adhoc" line="1" stmtstart="84" sqlhandle="0x01000700b7c78e0760dd3f81000000000000000000000000">
EXEC @RETURN_VALUE = [dbo].[UpdateUserStats] @UserID = @p0    </frame>
                    <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
                </executionStack>
                <inputbuf>
(@p0 int,@RETURN_VALUE int output)EXEC @RETURN_VALUE = [dbo].[UpdateUserStats] @UserID = @p0   </inputbuf>
            </process>
            <process id="process86ce0988" taskpriority="0" logused="10000" waittime="1806" schedulerid="1" kpid="2604" status="suspended" spid="72" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2010-05-31T12:17:37.663" lastbatchcompleted="2010-05-31T12:17:37.663" clientapp=".Net SqlClient Data Provider" hostname="WIN-S41KV2CLS67" hostpid="6920" loginname="sdfkj93jks9sl" isolationlevel="read committed (2)" xactid="91418" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
                <executionStack>
                    <frame procname="adhoc" line="1" stmtstart="74" sqlhandle="0x02000000de1cb30b5b2e40e31ffb345af3c7529430b559c2">
*password-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------     </frame>
                    <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
                </executionStack>
                <inputbuf>
*password--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------    </inputbuf>
            </process>
        </process-list>
        <resource-list>
            <pagelock fileid="1" pageid="13921" dbid="7" objectname="database.dbo.Users" id="lock85535c80" mode="IX" associatedObjectId="72057594046382080">
                <owner-list>
                    <owner id="process8765fb88" mode="IX"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process824df048" mode="S" requestType="wait"/>
                </waiter-list>
            </pagelock>
            <pagelock fileid="1" pageid="14196" dbid="7" objectname="database.dbo.Users" id="lock8469f980" mode="SIU" associatedObjectId="72057594046382080">
                <owner-list>
                    <owner id="process86ce0988" mode="S"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process8765fb88" mode="IX" requestType="convert"/>
                </waiter-list>
            </pagelock>
            <exchangeEvent id="Pipe894b0680" WaitType="e_waitPipeGetRow" nodeId="0">
                <owner-list>
                    <owner id="process824df048"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process86ce0988"/>
                </waiter-list>
            </exchangeEvent>
        </resource-list>
    </deadlock>

Я много читал о тупиках ... И я не понимаю, почему это вызывает тупик.

Так что оба этих запроса выполняются очень часто. По крайней мере, раз в секунду. Может быть, даже чаще (300-400 пользователей онлайн). Таким образом, они могут быть запущены одновременно очень легко, но почему это вызывает тупик? Пожалуйста, помогите.

Спасибо

Ответы [ 3 ]

11 голосов
/ 31 мая 2010

Вам нужно захватить график тупиковой ситуации. Подключите Profiler и запишите событие Граф тупиковых событий . Сохраните график .XDL и добавьте эту информацию в свой пост.

До тех пор, вполне очевидно, что для вашего запроса DB.Users.SingleOrDefault требуется индекс по крайней мере для имени, если не для имени и пароля:

CREATE INDEX idxUsersNamePassword on Users(Name,Password);

Я ожидаю, что у Пользователей уже есть индекс по ID, а у Статей есть индекс по ArticleID, который также охватывает AuthorID. Предполагая, что Users.ID и Articles.ArticleID являются PK в соответствующих таблицах, они, вероятно, являются кластеризованным ключом соответствующего, так что это правда. Стоит, однако, дважды проверить.

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

ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON

Кроме того, сохранение пароля в виде открытого текста является основной ошибкой.

Обновление после информации о взаимоблокировке

Существует три процесса (запроса):

  • A) ... F048, на котором запущен SELECT ... FROM Users WHERE Password = ... and Name = ...
  • B) ... 0988, на котором запущен SELECT ... FROM Users WHERE Password = ... and Name = ...
  • C) ... FB88 под управлением UPDATE ...

Цикл тупика:

  1. C ожидает блокировки страницы IX, блокируется S-блокировкой A
  2. B ожидает блокировки страницы S, блокируется блокировкой IX's C
  3. A ожидает на параллельных ресурсах обмена, блокируется B

Цикл поэтому C-> A-> B-> C.

Из того факта, что два задействованных SELECT решили: 1) использовать параллельный план и 2) использовать блокировки страниц, очевидно, что они выполняют сквозное сканирование всей таблицы Users. поэтому, как я и предполагал, проблема заключается в отсутствии индекса (имя, пароль) для пользователей, что приводит к тому, что запрос сканирует слишком много данных. Добавление индекса превратило бы SELECT в прямую SEEK для индекса Nc и поиска для кластеризованного индекса, и это значительно уменьшило бы окно наложения с UPDATE. В настоящее время UPDATE гарантированно конфликтует с всеми SELECT, поскольку каждый SELECT гарантированно читает каждую строку.

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

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

1 голос
/ 31 мая 2010

В этой ситуации (т. Е. Тип данных, которые вы читаете, и характер обновлений, происходящих с этими данными), я выполняю запрос поиска пользователя при отключенной изоляции чтения.

В качестве альтернативы, более сложное изменение. Исходя из описания, которое вы разместили, я бы не учел количество просмотров в записи пользователя. Вместо этого я бы записал ViewCount против Article, а затем получил бы общее количество просмотров для пользователя из суммы Articles.ViewCount по AuthorID.

1 голос
/ 31 мая 2010

Ваша проблема имеет много параллелей с этим здесь Диагностика взаимоблокировок в SQL Server 2005

(Linq to SQL, транзакция только для чтения заблокирована транзакцией чтения-записи)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...