Большинство администраторов баз данных, независимо от того, с какой платформой они работают, знакомы с концепцией событий ожидания. События ожидания — естественная часть процесса обработки запросов ядром базы данных. При установлении соединения с ядром базы данных назначается идентификатор сеанса (SPID), что позволяет направлять запросы.
Каждый запрос может пребывать в одном из трех состояний: выполняемый (running), готовый к выполнению (runnable) или приостановленный (suspended). «Приостановленный» означает, что сеанс с данным идентификатором SPID ожидает предоставления ресурса, скажем, страницы, которую нужно будет считать с диска в память. Когда сеанс с идентификатором SPID приостанавливается, причина ожидания регистрируется системой как событие ожидания. «Готовый к выполнению» означает, что сеанс с данным SPID ожидает предоставления доступа к планировщику, обычно именуемому процессором. Значение термина «выполняемый» прямое: сеанс с соответствующим идентификатором SPID в данный момент выполняется.
В колледже я увлекался математикой, отсюда моя любовь к объединению объектов в группы. Именно поэтому, когда я разъясняю концепцию событий ожидания, то предпочитаю представлять их группами. Первые группы — выполняемые, готовые к выполнению и приостановленные запросы — мы уже рассмотрели. Следующий набор групп — это собственно события ожидания, и я разделю их на следующие категории: внутренние, ресурсные и внешние.
Поясню, что каждая из этих групп представляет собой, с моей точки зрения.
Внутреннее ожидание
Среди событий внутреннего ожидания обычно выделяют запирание (locking), блокировку (blocking) и взаимоблокировку (deadlocking). Такие события, как запирание, блокировка и взаимоблокировка, происходят вследствие изоляции транзакций — необходимой установки для реляционных баз данных, которые сохраняют принципы ACID-транзакции. На протяжении ряда лет меня время от времени привлекали к выявлению причин снижения быстродействия систем, и я обнаружил, что пользователи просто сталкиваются с эффектом выбранного уровня изоляции: по умолчанию для Microsoft SQL Server используется уровень READ COMMITTED, а для Microsoft Azure SQL Database — уровень READ COMMITTED SNAPSHOT.
В зависимости от применяемого уровня изоляции производительность и пропускная способность приложения могут меняться. Это не означает, что в случае обнаружения запираний и блокировок уровень изоляции транзакций следует изменять. Необходимо выяснить, почему происходит запирание. Здесь я хочу отметить, что рассматриваемая группа проблем, связанных с внутренним ожиданием, скорее всего, не решается путем замены оборудования. Хотя возможно, что первопричина состоит в некоем ограничении по ресурсам, как будет показано ниже. Перед тем как расходовать тысячи долларов на новое оборудование с целью «устранения» запираний, блокировок или взаимоблокировок, имеет смысл подумать о внесении изменений в код и схему.
Ожидание доступа к ресурсам
Теперь у нас речь пойдет о более известных вариантах ожидания с такими именами, как PAGEIOLATCH_EX или CXPACKET. Нам часто приходится слышать о них. Эти категории ожидания регистрируются, когда сеанс попадает в приостановившуюся очередь, как описано выше. Я отношу подобные случаи к категории «ожидание доступа к ресурсам», потому что они имеют отношение к одному из четырех основных типов физических аппаратных ресурсов: процессор, память, диск и сеть.
Относительно проблем, связанных с ожиданием доступа к ресурсам, вам следует знать, что они имеют только два решения. Первое решение — используйте меньше ресурсов. И второе — приобретайте больше. Возьмем для примера физическую память. Если у вас имеется запрос, потребляющий весь пул буфера так, что выполнение каждого второго запроса приводит к снижению быстродействия, вам придется либо соответствующим образом настроить запрос, либо оснастить систему дополнительной памятью. Как только вы начнете рассматривать ситуации, связанные с ожиданием доступа к ресурсам, под этим углом зрения, вам станет проще принимать решения относительно действий, которые помогут справиться с проблемой.
Внешние ожидания
Такие ожидания регистрируются в ситуациях, когда сеанс выполняется. Они легко выявляются, поскольку их имена всегда начинаются со слова PREEMPTIVE. Подобные ожидания известны как «некооперативные ожидания», потому что система SQL Server оказывается вынужденной отказаться от контроля регламентации этих заданий. Часто такие ситуации возникают в результате того, что системе SQL Server требуется внешний — и отсюда предложенное мною название для данной группы — вызов операционной системе с запросом к внешней хранимой процедуре или использование объектов SQL CLR. Любопытно отметить, что, хотя с точки зрения SQL Server задание выполняется, SQL Server не имеет представления о статусе удаленного вызова. Это затрудняет диагностику упомянутых событий ожидания.
Еще более осложняет ситуацию то, что данные события ожидания не слишком хорошо документированы. Лучший способ анализа подобных событий — выполнить поиск по имени соответствующего события, удалив из него слово PREEMPTIVE. Так, для анализа события ожидания PREEMPTIVE_OS_GETADDRINFO зайдите на сайт Windows Dev Center и выполните поиск подстроки GETADDRINFO.
Я считаю, что метод объединения событий в группы позволяет мне быстрее добираться до первопричины проблемы. В отношении событий ожидания этот метод в сочетании с использованием средств анализа на базе ожидания верно служил мне на протяжении многих лет. В рамках того же процесса, что и дерево решений, мы можем брать событие ожидания, назначать ему группу и получать варианты подстройки, которыми можем воспользоваться с этого момента. Конечный результат — эффективное использование ресурсов для решения проблемы, но еще важнее то, что мы получаем возможность устранять неполадки в более сжатые сроки.