ГлавнаяБлогОтключение событий Add/Update/Delete в 1С-Битрикс: почему raw SQL и EventManager — оба не то

Отключение событий Add/Update/Delete в 1С-Битрикс: почему raw SQL и EventManager — оба не то

Рамиль Юналиев
Рамиль Юналиев
E-Commerce Lead
3 июля 2026 г.
2 мин чтения

В 2014-м у меня был пост про обход событий *::Add, *::Update, *::Delete через прямой SQL-запрос к b_user. Трюк рабочий, но за 10 лет на high-load проектах он не раз стрелял в ногу.

Почему raw SQL — плохая идея

CUser::Update() не просто пишет в базу. Событие OnAfterUserUpdate — стандартная точка расширения ядра, и на боевых проектах на неё почти всегда что-то повешено: синхронизация с CRM, сброс кеша, вебхук во внешнюю систему. Ядро само по себе на этом событии ничего не делает — это чужой (или свой же) обработчик, добавленный отдельно. Прямой UPDATE b_user молча пропускает вообще всё, что на это событие подписано.

Баг всплывает не сразу — где-то раз в квартал, когда клиент спрашивает, почему в CRM данные пользователя не обновились. Найти причину сложно: код формально работает, ошибок нет, просто один из подписанных обработчиков никогда не срабатывает.

Соблазн: "просто отключим через EventManager"

Первая мысль в D7 — временно снять обработчик через \Bitrix\Main\EventManager и вернуть обратно:

// НЕ ДЕЛАЙ ТАК
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->removeEventHandler('main', 'OnAfterUserUpdate', ['CrmSync', 'onUserUpdate']);
$user = new \CUser();
$user->Update($ID, $arFields);
$eventManager->registerEventHandler('main', 'OnAfterUserUpdate', 'my_module', 'CrmSync', 'onUserUpdate');

Не работает и опасно сразу по двум причинам:

  1. removeEventHandler($entity, $eventType, $iEventHandlerKey) снимает обработчик, который живёт только в памяти текущего запроса и исчезает вместе с ним — ему нужен точный ключ обработчика, а не класс с методом. Обработчик CRM обычно зарегистрирован постоянно, в базе, при установке модуля — removeEventHandler его вообще не увидит, событие всё равно сработает.
  2. registerEventHandler(...) — это постоянная регистрация в базе, предназначена для install-скрипта модуля, не для «включить обратно после одной операции». Вызывать её на каждый апдейт — не по назначению.

И главное — гонка: если процесс упадёт между "снял" и "вернул", обработчик останется выключенным для всех запросов, а не только для твоего. На нагруженном проекте это не редкий случай, а вопрос времени.

Что реально работает

D7 не даёт готового безопасного способа временно выключить чужой постоянный обработчик. Рабочий способ — тот же, что я описывал в посте про отключение обработчиков: статический флаг внутри самого обработчика.

class CrmSync
{
    public static $enabled = true;
 
    public static function onUserUpdate(&$arFields)
    {
        if (!self::$enabled) {
            return;
        }
        // основная логика синхронизации
    }
}
 
// точечно, только вокруг нужной операции
CrmSync::$enabled = false;
$user = new \CUser();
$user->Update($ID, $arFields);
CrmSync::$enabled = true;

Требует доступа к коду обработчика (свой модуль или патч чужого), зато безопасно: флаг живёт в памяти одного процесса/запроса, гонки между запросами нет, ничего не трогает в персистентной регистрации.

Когда raw SQL всё ещё оправдан

Массовые операции (тысячи записей за раз), где события намеренно не нужны ни одному подписчику — разовый импорт, миграция, консольный скрипт вне веб-запросов. Для точечного изменения одного пользователя в проде — используй флаг, не SQL и не EventManager::registerEventHandler.