max server memory ограничен, а sqlservr.exe ест больше: куда уходит память SQL Server
Раз в квартал у кого-нибудь из клиентов повторяется один и тот же разговор: «мы поставили max server memory в 24 гигабайта, а в диспетчере задач sqlservr.exe жрёт 28 — у вас там утечка». Утечки нет. Есть непонимание того, что именно измеряет этот параметр. Ниже — как я объясняю это техническим специалистам и руководителям клиентов, с конкретными DMV-запросами, формулами и тем, что в лимит принципиально не попадает.
Кейс, с которого всё началось
У одного из наших клиентов (юрлицо на 40 рабочих мест, типовая связка «1С:Предприятие 8.3 + MS SQL Server на одной физической машине») мы подняли RAM сервера с 16 до 32 ГБ и подняли max server memory (MB) с 12288 до 24576. Через три дня работы под нагрузкой бухгалтер прислал скриншот диспетчера задач: строка sqlservr.exe — 27,4 ГБ. Вопрос закономерный: «вы настроили лимит 24 ГБ, почему он его не соблюдает?»
Это тот самый повторяющийся паттерн, из-за которого я и сел писать этот материал: администратор сравнивает Target Server Memory (то, что регулирует менеджер памяти SQL Server) с полным потреблением процесса sqlservr.exe в Windows — и любое расхождение записывает в утечку. На деле это два разных измерения, и разница между ними объясняется документированным списком категорий памяти, которые менеджер памяти SQL Server сознательно не контролирует.
Настроим память SQL Server и 1С под вашу реальную нагрузку
Если у вас на сервере 1С и MS SQL Server живут вместе, а диспетчер задач показывает цифры, которые не бьются с настроенным max server memory — пришлите нам вывод sys.dm_os_process_memory и sys.dm_os_memory_clerks через форму обратной связи на itfresh.ru. Разберём конкретно вашу конфигурацию и посчитаем корректный лимит под вашу нагрузку.
Что физически ограничивает max server memory (MB)
Официальная документация Microsoft описывает область действия max server memory (MB) и min server memory (MB) как границы для буферного пула и «прочих кэшей ядра БД». В более развёрнутом виде — в Memory Management Architecture Guide — это сформулировано так: настройка «управляет выделением памяти SQL Server, compile-памятью, всеми кэшами (включая буферный пул), грантами памяти на выполнение запросов, памятью диспетчера блокировок и памятью CLR — фактически любым клерком памяти, который виден в sys.dm_os_memory_clerks».
Начиная с SQL Server 2012 (11.x) в эту область добавили ещё и CLR-аллокации — до этого CLR-память жила отдельно. Если у вас в 1С используются внешние компоненты или CLR-функции (не типично для типовых конфигураций, но встречается в доработках), с 2012-й версии их память тоже учитывается лимитом.
| Компонент | Входит в max server memory? | Где смотреть |
|---|---|---|
| Буферный пул (страницы данных и индексов) | Да, основной потребитель | MEMORYCLERK_SQLBUFFERPOOL в sys.dm_os_memory_clerks |
| План-кэш (кэш планов запросов, adhoc/proc cache) | Да | CACHESTORE_SQLCP, CACHESTORE_OBJCP |
| Compile-память и гранты памяти на сортировки/хэши | Да | sys.dm_exec_query_memory_grants |
| Память диспетчера блокировок | Да | sys.dm_os_memory_clerks, категория LOCK |
| CLR (с SQL Server 2012 и новее) | Да | MEMORYCLERK_SQLCLR |
| Columnstore и In-Memory OLTP (Hekaton) объекты | Да, но с отдельными клерками для мониторинга | MEMORYCLERK_XTP*, клерки columnstore |
Отдельно обращаю внимание на нюанс, который я нашёл при сверке текущей редакции документации: формулировка «Server Memory Configuration Options» в разделе про сам параметр сузилась до «max server memory ограничивает только размер буферного пула SQL Server», а память под расширенные хранимые процедуры, COM-объекты и несовместно загруженные DLL прямо названа «нерезервируемой областью», которую лимит не трогает. Это не противоречие, а уточнение: широкий список из architecture guide описывает, что менеджер памяти SQL Server учитывает как «серверную» память (Total/Target Server Memory), а формулировка в Server Memory Configuration Options напоминает, что процесс sqlservr.exe в целом шире этой границы.
Ещё один источник ложных тревог — рост план-кэша на боевой базе 1С. Каждый уникальный текст запроса, который платформа 1С формирует динамически (а генератор запросов 1С этим славится — параметризация отличается от вызова к вызову), создаёт отдельный вход в CACHESTORE_SQLCP. За несколько недель без чистки план-кэш на активной базе может вырасти до нескольких гигабайт — это не утечка, а результат накопления planов, и находится внутри лимита max server memory, просто занимает в нём растущую долю от буферного пула. Диагностируется тем же запросом к sys.dm_os_memory_clerks из раздела про диагностику — по имени клерка сразу видно, что выросло: буферный пул или план-кэш.
Как эта граница менялась начиная с SQL Server 2012 — и почему это важно после апгрейда
До версии 2012 (11.x) SQL Server считал память через пять разных механизмов, и лимитом были охвачены далеко не все из них. Таблица ниже — прямая выжимка из Memory Management Architecture Guide, она объясняет, почему после миграции старой базы 1С с сервера SQL 2008 R2 на SQL 2019 или 2022 у клиентов резко меняется фактическое потребление при том же значении max server memory.
| Тип аллокации | SQL Server 2005 / 2008 / 2008 R2 | SQL Server 2012 и новее |
|---|---|---|
| Single-Page Allocator (блоки ≤ 8 КБ) | Учитывается лимитом | Учитывается (объединено в «any size» allocator) |
| Multi-Page Allocator (блоки > 8 КБ) | Не учитывается | Учитывается |
| CLR-аллокации | Не учитывается | Учитывается |
| Стеки потоков (thread stacks) | Не учитывается | Не учитывается |
| Прямые аллокации Windows (DWA: xp_ DLL, sp_OA, linked server) | Не учитывается | Не учитывается |
Вывод для практики: если вы когда-то подбирали max server memory «на глаз» под SQL Server 2008 R2 и просто перенесли то же число на новый сервер 2019/2022/2025 — пересчитайте. С 2012 версии под тем же значением лимита реально помещается меньше нагрузки, потому что туда же теперь попадают multi-page и CLR аллокации, которые раньше жили в «нерезервируемой» зоне (о ней — в следующем разделе).
Пять категорий памяти, которые лимит не видит принципиально
Вот полный список, который я держу перед глазами при разборе жалоб «сервер ест больше лимита», собранный по Memory Management Architecture Guide и Server Memory Configuration Options:
| Категория | Что конкретно | Как оценить объём |
|---|---|---|
| Стеки потоков worker'ов | Каждый поток SQL Server резервирует стек фиксированного размера | Размер стека × число worker-потоков (формула ниже) |
| memory_to_reserve (VAS-резерв) | Область адресного пространства под multi-page/CLR/стеки/DWA до 2012, сейчас — под стеки и DWA | По умолчанию 256 МБ, sp_configure 'memory to reserve' |
| DLL расширенных хранимых процедур, объекты sp_OA, провайдеры Linked Server | Сторонний код, загруженный в адресное пространство sqlservr.exe | Инвентаризация xp_-процедур и связанных серверов в конфигурации |
| Буферы резервного копирования | Выделяются под операции BACKUP/RESTORE | Зависит от BUFFERCOUNT и MAXTRANSFERSIZE в задании бэкапа |
| Кратковременный over-commit сверх Target Server Memory | При нехватке непрерывной памяти для multi-page запросов (rebuild columnstore-индекса, batch mode, большие гранты) | Сравнение счётчиков Total Server Memory и Target Server Memory в perfmon |
Формула для стеков актуальна почти всегда, поэтому распишу её отдельно. Размер стека на 64-битном SQL Server на 64-битной ОС — 2048 КБ на поток (для Itanium было 4096 КБ, для 32-битного SQL Server на 64-битной ОС — 768 КБ; в 2026 году это уже история, но полезно знать при аудите legacy-инсталляций). Число потоков — это рассчитанное значение max worker threads, которое зависит от количества логических процессоров, закреплённых за инстансом. Пример: сервер с 8 CPU обычно получает порядка 512 worker threads по умолчанию, значит под стеки резервируется около 512 × 2048 КБ ≈ 1 ГБ адресного пространства. Это резерв виртуального адресного пространства, но по мере роста нагрузки часть его реально коммитится в физическую память — и именно эта часть добавляется к тому, что вы видите в Task Manager поверх Target Server Memory.
Отдельно стоит multi-page over-commit: начиная с SQL Server 2012, если для запроса нужен блок памяти больше 8 КБ, а из-за фрагментации нет подходящего непрерывного куска в рамках лимита, SQL Server временно выделяет память сверх max server memory, а затем Resource Monitor старается вернуть Total Server Memory к Target Server Memory. Такое поведение типично при перестроении columnstore-индексов, batch mode на rowstore и крупных грантах памяти. В SQL Server 2019 для ускорения этой уборки есть трейс-флаг 8121, а начиная с SQL Server 2022 (16.x) это поведение включено по умолчанию без флага.
Про буферы резервного копирования — цифры, которые я привожу клиентам, чтобы разговор был предметным, а не абстрактным: объём памяти под один поток backup примерно равен MAXTRANSFERSIZE × BUFFERCOUNT. При значениях по умолчанию (обычно 1 МБ на MAXTRANSFERSIZE и небольшое число буферов) это единицы-десятки мегабайт, но если задание бэкапа явно завышает BUFFERCOUNT ради скорости на быстрых дисковых массивах, кратковременный расход памяти на операцию бэкапа может доходить до сотен мегабайт сверх max server memory — и это ожидаемое, документированное поведение, а не повод искать утечку.
Совмещённый сервер 1С + SQL Server: наш типовой случай у клиентов до 50 РМ
У большинства наших клиентов сервер 1С:Предприятие и MS SQL Server живут на одной машине — отдельный SQL-хост экономически не оправдан при штате до 50 человек. Здесь к описанным выше пяти категориям добавляется ещё один источник путаницы: процессы кластера 1С (rphost.exe, rmngr.exe, ragent.exe) — это отдельные процессы, их память вообще не относится ни к sqlservr.exe, ни к max server memory. Но в диспетчере задач директор смотрит на сервер целиком и складывает всё подряд, получая пугающую сумму.
Формула, по которой я считаю max server memory на совмещённом сервере, отталкивается от подхода из официальной рекомендации Microsoft (вычесть из общего объёма память ОС, стеки, DWA), но добавляет отдельную строку под 1С:
- Общий физический объём RAM сервера.
- Минус 2–4 ГБ на операционную систему и службы мониторинга/бэкапа (Zabbix-агент, антивирус, Veeam agent).
- Минус ожидаемый рабочий набор кластера 1С (rphost + rmngr) — на практике для 20–40 активных сессий это от 3 до 8 ГБ, смотрю по факту через Resource Monitor за неделю пиковой нагрузки.
- Минус резерв под стеки потоков SQL Server (см. формулу выше, обычно 0,5–1,5 ГБ).
- Минус 20–25% на прочие DWA-аллокации (backup-буферы, xp_-процедуры, линки), как рекомендует Microsoft в качестве обобщённой оценки.
- Остаток — и есть значение max server memory (MB) для SQL Server.
На практике для сервера с 32 ГБ RAM под связку 1С + SQL Server на 35–40 пользователей это чаще всего даёт диапазон 20–24 ГБ под max server memory — это оценка по нашей практике внедрений, не норматив Microsoft, подбирается по факту нагрузки на конкретном сервере.
Диагностика: четыре запроса, которыми я подтверждаю клиенту, что утечки нет
Вместо того чтобы объяснять на словах, я показываю клиенту вывод DMV — цифры убеждают лучше, чем презентация. Вот минимальный набор, который закрывает 90% споров «куда делась память».
1. Сколько сервер реально считает своим (Total/Target Server Memory) и что в процессе сверх этого:
SELECT physical_memory_in_use_kb / 1024 AS physical_memory_in_use_mb,
large_page_allocations_kb / 1024 AS large_page_mb,
locked_page_allocations_kb / 1024 AS locked_page_mb,
virtual_address_space_committed_kb / 1024 AS vas_committed_mb,
memory_utilization_percentage,
process_physical_memory_low,
process_virtual_memory_low
FROM sys.dm_os_process_memory;Если process_physical_memory_low и process_virtual_memory_low равны 0 — процесс не испытывает дефицита, а разница между этим выводом и max server memory объясняется именно категориями из предыдущего раздела, а не утечкой.
2. Что происходит на уровне всей ОС (свободно ли системе памяти в принципе):
SELECT total_physical_memory_kb / 1024 AS total_physical_mb,
available_physical_memory_kb / 1024 AS available_physical_mb,
system_memory_state_desc
FROM sys.dm_os_sys_memory;3. Топ-клерки памяти внутри SQL Server — что именно занимает Target Server Memory:
SELECT TOP 15 type,
name,
pages_kb / 1024 AS pages_mb
FROM sys.dm_os_memory_clerks
ORDER BY pages_kb DESC;Если наверху этого списка не буферный пул и не план-кэш, а что-то нетипичное — вот здесь уже стоит разбираться предметно, но это уже не вопрос «max server memory не работает», а вопрос «какой именно кэш разросся».
4. Значения текущего лимита и то, что реально применено:
SELECT [name], [value], [value_in_use]
FROM sys.configurations
WHERE [name] IN ('max server memory (MB)', 'min server memory (MB)');Дополнительно смотрю журнал ошибок SQL Server на сообщение 17890 («A significant part of sql server process memory has been paged out») — если оно есть, это признак, что физической памяти на сервере не хватает уже на уровне ОС, и здесь действительно нужно либо снижать max server memory, либо добавлять RAM, либо включать Lock Pages in Memory (следующий раздел).
Lock Pages in Memory и большие страницы — когда это нужно совмещённому серверу
Lock Pages in Memory (LPIM) — это право Windows (SeLockMemoryPrivilege), которое позволяет sqlservr.exe держать буферный пул в физической памяти и не выгружаться на диск при внешнем давлении на память со стороны ОС. С SQL Server 2012 (11.x) трейс-флаг 845 для этого больше не нужен даже в Standard Edition — достаточно выдать привилегию учётной записи службы SQL Server.
Важный момент из документации: LPIM не меняет динамическое управление памятью — буферный пул по-прежнему растёт и сжимается по запросу других клерков. Но если вы включаете LPIM, Microsoft прямо рекомендует обязательно задать конкретное значение max server memory, а не оставлять значение по умолчанию 2147483647 МБ — иначе на совмещённом сервере с 1С заблокированный буферный пул может выесть память, нужную процессам rphost/rmngr, и словить нестабильность уже на стороне 1С.
Проверить текущий режим памяти можно одним запросом:
SELECT sql_memory_model_desc FROM sys.dm_os_sys_info;Три возможных значения: CONVENTIONAL — LPIM не выдан; LOCK_PAGES — выдан и используется; LARGE_PAGES — LPIM выдан в Enterprise-режиме вместе с трейс-флагом 834 (большие страницы; в документации отдельно отмечено, что это продвинутая конфигурация, не рекомендуемая для большинства сред). На типовых серверах наших клиентов до 50 РМ большие страницы не включаю — выигрыш не окупает рост сложности диагностики, а вот LPIM + явный max server memory ставлю по умолчанию на виртуальных машинах, где гипервизор потенциально может забирать память хоста.
Пять ошибок в интерпретации, которые я вижу чаще всего
За несколько лет разборов подобных обращений сложился короткий список типичных заблуждений — привожу его, чтобы вы могли сверить свой случай, прежде чем звать инженера.
- Working Set в диспетчере задач принимают за Target Server Memory. Working Set — это физически резидентная память процесса в моменте, она колеблется с паузами Windows на trim рабочего набора и не равна ни Total, ни Target Server Memory SQL Server. Ориентироваться нужно на
sys.dm_os_process_memory, а не на столбец «Память» в Task Manager. - Забывают про файл подкачки. Если
available_physical_memory_kbвsys.dm_os_sys_memoryнизкий, но page file ещё не исчерпан, система пока не в критическом состоянии — но это сигнал пересмотреть min server memory на инстансе. - Ожидания RESOURCE_SEMAPHORE трактуют как нехватку RAM на сервере. На деле это конкуренция запросов за гранты памяти внутри уже выделенного SQL Server пространства — лечится не добавлением RAM в лоб, а разбором тяжёлых сортировок/хэшей и статистики, либо точечным поднятием max server memory, если сервер объективно недогружен по факту.
- Сравнивают показатели разных инстансов на одной машине без поправки на общий пул. Если на сервере два инстанса SQL Server (например, боевой и тестовый для 1С), у каждого свой max server memory, и сумма обоих лимитов должна быть меньше физической RAM с запасом на ОС и на 1С — иначе оба инстанса будут одновременно испытывать memory pressure.
- Не разделяют NUMA-узлы при анализе. На серверах с несколькими NUMA-узлами буферный пул распределяется по узлам, и локальный дефицит памяти на одном узле может выглядеть как общая нехватка, хотя суммарно Target Server Memory ещё не выбран.
Пошаговый регламент, который я применяю при настройке памяти у клиента
Ниже — последовательность, которую использую при каждом внедрении или пересмотре конфигурации памяти на сервере с 1С и SQL Server:
- Снимаю фактическую нагрузку за неделю: пиковый рабочий набор rphost/rmngr через Resource Monitor и Total/Target Server Memory через perfmon-счётчики SQL Server.
- Считаю max server memory по формуле из раздела про совмещённый сервер (RAM минус ОС минус 1С минус стеки минус 20–25% на DWA).
- Проверяю редакцию SQL Server и версию — от неё зависит расчёт max worker threads и, соответственно, резерв под стеки потоков.
- Применяю значение через T-SQL, без перезапуска службы:
EXECUTE sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
EXECUTE sp_configure 'max server memory', 20480; -- 20 ГБ, пример
GO
RECONFIGURE;
GO- Задаю min server memory на уровне 40–50% от max — это не даёт SQL Server отдавать буферный пул слишком агрессивно при кратковременных всплесках нагрузки со стороны 1С (например, при регламентных операциях закрытия месяца).
- Если сервер виртуальный — выдаю LPIM учётной записи службы и повторно фиксирую явное значение max server memory (см. предыдущий раздел).
- Через неделю повторяю снятие метрик через
sys.dm_os_process_memoryиsys.dm_os_memory_clerks, сравниваю с базовой линией, при необходимости корректирую значение на 10–15% в любую сторону.
Отчёт клиенту в итоге состоит из трёх цифр: Target Server Memory (что регулирует лимит), физическая память процесса sqlservr.exe по sys.dm_os_process_memory, и отдельно — рабочий набор процессов 1С. Когда эти три цифры разложены по полочкам, вопрос «а где утечка» обычно закрывается сам собой.
Частые вопросы
- Правда ли, что max server memory ограничивает вообще всю память процесса sqlservr.exe?
- Нет. Лимит регулирует память, которой управляет менеджер памяти SQL Server: буферный пул, все кэши включая план-кэш, compile-память, гранты памяти на запросы, диспетчер блокировок и CLR (начиная с SQL Server 2012). Стеки потоков, DLL расширенных хранимых процедур, объекты sp_OA, провайдеры Linked Server и буферы резервного копирования живут вне этого лимита и добавляются к общему потреблению процесса в диспетчере задач.
- Сколько памяти закладывать на стеки потоков при расчёте лимита?
- На 64-битном SQL Server на 64-битной ОС размер стека — 2048 КБ на поток. Умножьте это на рассчитанное значение max worker threads для вашего числа логических процессоров — получите резерв, который стоит вычесть из общего объёма RAM ещё до расчёта max server memory.
- Нужно ли включать Lock Pages in Memory на каждом сервере с SQL Server?
- На виртуальных машинах, где гипервизор хоста может изымать память у гостя, LPIM оправдан почти всегда — но только вместе с явно заданным max server memory. На физическом сервере с достаточным запасом RAM и стабильной нагрузкой можно обойтись без LPIM, если в журнале ошибок нет сообщения 17890 о вытеснении памяти на диск.
- Что делать, если 1С и SQL Server стоят на одной машине и памяти постоянно не хватает?
- Считать max server memory не как «всю RAM минус немного на ОС», а с явным вычетом рабочего набора процессов rphost/rmngr кластера 1С, снятого за неделю пиковой нагрузки через Resource Monitor. Эти процессы отдельные от sqlservr.exe и в лимит SQL Server не входят, но конкурируют с ним за физическую память сервера.
- Как быстро проверить, куда ушла лишняя память, без остановки сервера
- Три запроса без даунтайма: sys.dm_os_process_memory — физическая память процесса и признаки нехватки памяти; sys.dm_os_memory_clerks с сортировкой по pages_kb — что конкретно занимает Target Server Memory внутри SQL Server; sys.configurations — текущее значение max server memory и min server memory. Вместе они за минуту показывают, укладывается ли сервер в лимит или действительно есть проблема.