SQL-агент без риска для продакшен-базы: readonly и лимиты
SQL-агент без риска для продакшен-базы: как настроить readonly, allowlist запросов, таймауты и быстрые проверки перед запуском.

Почему SQL-агент опасен без рамок
Фраза "SQL-агент без риска для продакшен-базы" звучит правдоподобно только тогда, когда у агента есть жесткие границы. Без них он пытается ответить на вопрос любой ценой. Для модели это нормально. Для рабочей базы - нет.
Проблема в том, что агент редко ограничивается одной таблицей. Человек спрашивает: "Почему вчера просела выручка?" Агент может полезть не только в продажи, но и в возвраты, скидки, пользователей, события приложения и служебные логи. Он собирает полную картину, а не бережет базу. Один вопрос быстро превращается в серию тяжелых запросов.
Даже одна ошибка обходится дорого. Если агент забыл фильтр по дате или выбрал не ту таблицу, он читает лишние данные. Иногда это миллионы строк. Иногда - персональные данные, к которым вообще не планировали обращаться. Даже доступ только на чтение не убирает риск: растет нагрузка, забиваются соединения, обычные запросы приложения начинают работать медленнее.
Разница между нормальным и опасным поведением обычно простая:
- Нормально: узкий SELECT по заранее известным таблицам, с фильтром по дате, LIMIT и понятной целью.
- Плохо: SELECT * без условий, JOIN "на всякий случай", чтение служебных таблиц, сканирование всей истории.
- Совсем плохо: UPDATE, DELETE, INSERT, ALTER, DROP и любые команды, которые меняют данные или схему.
Продакшен страдает сразу. Сначала падает скорость: база тратит CPU и память на тяжелый запрос агента, а пользовательское приложение отвечает медленнее. Потом приходят таймауты, очередь соединений и жалобы команды, которая вообще не знала, что аналитический агент что-то запустил.
Есть и менее заметный риск. Агент может ответить уверенно, но на лишних или неподходящих данных. Например, он объединит заказы и сессии по плохому условию JOIN и покажет ложный рост конверсии. Ошибку заметят не сразу, потому что запрос формально отработал.
В банке, ритейле или телекоме это особенно неприятно. Рядом с метриками там часто лежат чувствительные поля, а лишняя нагрузка быстро бьет по рабочим сервисам. Поэтому одного readonly доступа мало. Если нет allowlist SQL-запросов, лимитов по времени и ограничений на объем чтения, агенту оставили слишком много свободы.
Хороший SQL-агент не должен быть смелым. Он должен быть скучным, предсказуемым и недоверчивым к каждому своему запросу.
Какие ограничения нужны сразу
Первое правило простое: агент ничего не меняет в продакшен-базе. Ему дают только чтение и сразу убирают любые команды, которые несут риск: INSERT, UPDATE, DELETE, TRUNCATE, ALTER, DROP и другие DDL-операции. Если агент ошибется в формулировке, он упрется в запрет, а не в ваши данные.
Но этого мало. Нельзя открывать агенту всю базу целиком даже в режиме readonly. Для аналитики обычно хватает одной-двух схем и нескольких таблиц или вьюх. Если оставить доступ ко всему, агент начнет ходить в служебные таблицы, архивы и данные, которые вообще не нужны для ответа.
Хорошая практика - завести отдельного пользователя с узкими правами. Такой пользователь видит, например, только reporting.sales, reporting.orders и пару справочников. Если в базе есть персональные данные, лишние поля лучше скрыть сразу или вынести для агента отдельные представления с уже очищенной структурой.
Нужен и жесткий предел на объем. Агент живет с лимитами по строкам и размеру ответа. Иначе даже обычный SELECT * по большой таблице утащит сотни тысяч строк, забьет канал и даст модели слишком много шума вместо результата.
Обычно хватает четырех ограничений: не больше 100-1000 строк на один ответ, запрет на SELECT * для широких таблиц, предел на число столбцов и обрезка слишком длинных текстовых полей.
Еще один обязательный барьер - таймаут на каждый запрос. Если запрос работает дольше нескольких секунд, система должна его остановить и вернуть понятную ошибку. Это защищает базу от тяжелых сканов и случайных запросов без фильтров.
Нормальный стартовый сценарий выглядит так: аналитик спрашивает выручку по регионам за прошлый месяц, агент идет только в разрешенные таблицы, выбирает нужные поля, получает до 500 строк и завершает работу за 3-5 секунд. Если он пытается сделать JOIN с огромным архивом или тянет весь справочник целиком, система режет запрос по времени или по объему.
Команды часто ошибаются одинаково: долго обсуждают промпты и почти не трогают ограничения на стороне SQL. Это плохой порядок. Сначала права, лимиты и allowlist. Потом уже качество формулировок.
Как настроить readonly доступ
Readonly доступ лучше настраивать так, будто агент однажды обязательно ошибется. Тогда даже плохой запрос не сможет удалить строки, обновить статус заказа или случайно запустить тяжелую служебную функцию.
Начните с отдельной роли только для чтения. Не давайте агенту логин аналитика, учетку бэкенда или общий пароль команды. У агента должна быть своя роль, свой секрет и свой набор прав. Так проще понять, кто именно ходил в базу, и проще быстро отключить доступ, если что-то пошло не так.
Лучше открывать агенту не сырые таблицы, а вьюхи. Во вьюхе можно заранее убрать лишние поля, скрыть персональные данные и оставить только те колонки, которые нужны для ответов. Если отдел продаж спрашивает про выручку по регионам, агенту не нужен прямой доступ к таблице с клиентскими профилями и внутренними комментариями.
Базовая схема прав обычно выглядит так:
- роль агента получает USAGE только на нужные схемы;
- SELECT дают только на подготовленные вьюхи;
- доступ к исходным таблицам закрывают;
- EXECUTE на функции проверяют отдельно и почти всегда отключают;
- создание таблиц, временных объектов и запись в базу запрещают.
Права на функции стоит проверить отдельно. Это частая дыра. Формально роль может быть readonly, но старая функция с правами владельца даст обходной путь к данным или даже к изменению записей. Если функция агенту не нужна, доступа к ней быть не должно.
Еще один слабый момент - старые креды. Команда нередко запускает агента на новом readonly-пользователе, но в конфиге остается прежний пароль от сервисной учетки с широкими правами. Агент сам ничего не обходит, но приложение легко делает это за него, если держит несколько строк подключения и выбирает не ту.
Перед запуском полезно проверить четыре вещи: агент подключается только под новой ролью, в секретах и переменных окружения нет старых учеток, через этот логин нельзя читать сырые таблицы, и запрос от имени агента не может вызвать опасную функцию.
Как работает allowlist запросов
Allowlist для SQL-агента - это набор четких правил: какие запросы агент может собрать и отправить в базу, а какие система сразу отклоняет. Такой подход надежнее, чем фильтровать опасный SQL по словам вроде DELETE или DROP. Вы заранее описываете нормальное поведение, а все остальное считаете запретным.
Обычно для аналитики достаточно SELECT и нескольких понятных операций поверх него: COUNT, SUM, AVG, MIN, MAX, GROUP BY, ORDER BY, LIMIT. Если команде не нужны подзапросы, UNION или оконные функции, не открывайте их "на всякий случай". Чем уже рамки, тем меньше сюрпризов.
Хороший allowlist работает на трех уровнях. Сначала вы фиксируете допустимые таблицы. Потом - поля, которые агент может читать. После этого описываете разрешенные JOIN: какие таблицы можно соединять и по каким условиям.
Например, агенту можно дать доступ только к orders, customers и regions. В customers разрешить customer_id и segment, но не phone и email. JOIN открыть только по orders.customer_id = customers.customer_id. Тогда запрос на выручку по сегментам пройдет, а попытка вытянуть лишние персональные данные упрется в правило еще до похода в базу.
Еще один слой защиты - убрать конструкции, которые аналитике часто не нужны, но часто ломают границы. Обычно блокируют комментарии, UNION, несколько выражений в одном запросе, DDL, DML, вызовы опасных функций и обращения к системным схемам. Комментарии тоже лучше резать: через них прячут обход фильтров и мусор, который ломает простой контроль.
Самая частая ошибка - проверять SQL простым поиском по строке. Это слабое место. Агент может обойти такой фильтр пробелами, регистром, комментариями или вложенной конструкцией. Надежнее разбирать запрос парсером, получать AST и уже по структуре проверять тип операции, список таблиц и колонок, вид JOIN, условие соединения, разрешенные функции и число выражений.
Если запрос не проходит хотя бы одно правило, система не должна пытаться исполнить "почти подходящий" вариант. Лучше сразу вернуть понятную ошибку и попросить агента собрать SQL заново в допустимых рамках. Так аналитика остается полезной, а продакшен-база не превращается в полигон для экспериментов.
Контроль времени и объема ответа
Readonly доступ не спасает, если агент может гонять тяжелые запросы без лимитов. Один неудачный JOIN по большой таблице или запрос без фильтра легко съест ресурсы базы и замедлит рабочие сервисы. Поэтому время выполнения и размер ответа лучше ограничить сразу, а не после первого сбоя.
Короткий timeout нужен на каждый запуск. Не на сессию целиком, а на каждый отдельный SQL-запрос. Для аналитических вопросов часто хватает нескольких секунд. Если агент не уложился, система должна остановить запрос, вернуть понятную ошибку и не пытаться дожимать базу повтором.
LIMIT тоже нужен почти всегда, даже когда вопрос кажется простым. Пользователь пишет: "Покажи последние заказы с ошибкой оплаты", а модель легко строит запрос, который тянет тысячи строк вместо 50. Для первого ответа лучше вернуть небольшой срез, а потом уже уточнять запрос. Так аналитик быстрее видит картину, а база не работает впустую.
Обычно хватает таких правил:
- ставить timeout 3-10 секунд на один запрос;
- добавлять
LIMITпо умолчанию, например 100 или 500 строк; - останавливать длинные запросы до часов пиковой нагрузки;
- логировать вопрос, SQL-текст и причину остановки.
Логи часто недооценивают. Они нужны не только для разбора инцидентов. По ним видно, какие вопросы агент понимает плохо, какие таблицы он трогает слишком часто и где сам промпт подталкивает модель к тяжелым запросам.
Простой пример: аналитик просит сравнить продажи по регионам за год. Агент строит запрос с лишними полями и сортировкой по большой выборке. Timeout обрывает его через 5 секунд, LIMIT не дает вернуть лишние строки, а лог сохраняет исходный вопрос и SQL. После этого команда убирает ненужную сортировку и добавляет более узкий шаблон запроса.
К таким ограничениям лучше относиться как к обычной части архитектуры. Это не запасной тормоз, а базовая защита.
Как запустить по шагам
Начинайте узко. Не давайте агенту доступ ко всем таблицам и всем вопросам сразу. Выберите один отчет, который команда и так строит вручную, и один источник данных под него.
Хороший первый сценарий выглядит скучно, и это плюс. Например, ежедневный отчет по продажам из одной реплики базы, где нужны только несколько таблиц, понятные фильтры и итог по дням. Чем уже задача, тем проще увидеть, где агент ошибается.
Потом соберите allowlist не "на всю аналитику", а только под этот сценарий. Разрешите чтение из конкретной схемы, оставьте нужные поля и заранее решите, какие типы запросов допустимы. Обычно хватает простых SELECT с фильтром по дате, группировкой и лимитом на число строк.
Дальше прогоните тестовые вопросы на копии данных или на безопасной реплике. Берите не только нормальные запросы, но и неудобные: расплывчатые формулировки, слишком широкие даты, попытки запросить лишние поля. Смотрите на три вещи: какой SQL строит агент, сколько времени идет запрос и сколько строк он возвращает.
Рабочий порядок обычно такой:
- Один отчет, один источник, одна группа таблиц.
- Узкий allowlist под этот отчет.
- Тесты на копии данных с реальными вопросами команды.
- Ручное подтверждение каждого запроса на первом этапе.
- Доступ для небольшой группы пользователей, а не для всей компании.
Ручное подтверждение на старте почти всегда окупается. Аналитик или инженер смотрит на SQL перед запуском и быстро ловит странные конструкции: лишний диапазон дат, неожиданный JOIN, выборку без LIMIT. Через неделю таких проверок обычно видно, что надо запретить жестче, а что уже можно открыть.
Когда сценарий ведет себя спокойно, дайте доступ маленькой группе. Подойдут 3-5 человек, которые действительно будут пользоваться этим каждый день и оставят нормальную обратную связь. Массовый запуск здесь только мешает. Сначала нужен чистый журнал запросов, понятные ошибки и несколько итераций над allowlist.
Если первый сценарий работает без сюрпризов две-три недели, добавляйте следующий. Не раньше.
Пример для команды аналитики
Менеджер по продажам пишет в чат: "Покажи продажи по регионам за последние 7 дней". Для человека это обычный вопрос. Для SQL-агента это должен быть узкий и предсказуемый запрос, а не повод ходить по всей продакшен-базе.
Поэтому агенту не дают доступ к сырым таблицам заказов, клиентов и оплат. Он читает только одну вьюху, где уже есть нужные поля: дата, регион, сумма продаж и число заказов. Этого хватает, чтобы ответить быстро и без лишнего риска.
Запрос получается простым: выбрать данные за 7 дней, сгруппировать по регионам и вернуть итог. Агент не видит персональные данные, не трогает историю за год и не лезет в соседние таблицы "на всякий случай".
На практике такой сценарий обычно держится на четырех правилах: доступ только к одной аналитической вьюхе, обязательный фильтр по дате, ограничение на число строк и короткий таймаут.
Если агент по ошибке пытается вытащить слишком много строк, защита срабатывает сразу. Например, он строит запрос без фильтра по дате или просит детализацию по каждому магазину, товару и дню. Система либо добавляет жесткий лимит, либо обрывает запрос до ответа. Пользователь получает короткое сообщение, а не подвисшую базу.
Иногда вопрос слишком широкий с самого начала. Менеджер пишет: "Сравни все продажи и найди, где есть отклонения". Нормальный агент не угадывает. Он просит уточнить период, метрику и разрез: регионы, магазины или категории.
Именно так и должен вести себя безопасный агент: читать только нужное, не тянуть лишнее и задавать встречный вопрос, если запрос расплывчатый.
Частые ошибки
На бумаге все выглядит просто: дать доступ только на чтение, добавить несколько правил и пустить агента в работу. На практике команды чаще ошибаются в базовых вещах.
Первая ловушка - выдать агенту ту же роль, что уже есть у аналитика. Человек обычно знает, какие таблицы лучше не трогать, и часто замечает странный запрос. Агент так не рассуждает. Если у роли есть доступ к десяткам схем, служебным таблицам и старым витринам, он начнет ходить везде, где дверь открыта.
Вторая ошибка - надеяться на readonly как на полную защиту. Он не портит данные, но легко вытаскивает лишнее. Если команде нужен один отчет по заказам, не надо открывать всю схему продаж, CRM и поддержку заодно. Лучше дать доступ к одной витрине или к нескольким представлениям с уже скрытыми полями.
Третья ошибка - пускать SQL как есть, без нормального разбора запроса и без allowlist. Тогда агент может собрать тяжелый JOIN, полезть в системные таблицы или отправить запрос, который никто не планировал. Даже сильная модель иногда выбирает странный путь, если вопрос пользователя сформулирован расплывчато.
Четвертая ошибка стоит денег и нервов: команды забывают про LIMIT, таймаут и предел на размер ответа. Один неудачный запрос к большой таблице может занять минуты, забить пул соединений и вернуть такой объем строк, который никто потом не прочитает.
Есть и тихая проблема, которую замечают поздно. Люди смотрят только на красивый ответ агента и почти не открывают логи. А смотреть нужно не только на итоговый текст, но и на сам SQL, время выполнения, число строк, отказанные запросы и попытки выйти за разрешенные таблицы.
Хороший признак зрелой настройки легко проверить: агент ходит только под отдельной readonly-ролью, видит не всю схему, а нужные представления, сервис режет запрос по allowlist, LIMIT и таймауту, а команда регулярно смотрит логи, а не только ответы.
Небольшой пример. Аналитикам нужен ежедневный отчет по продажам. Команда открывает агенту всю рабочую роль, потому что так быстрее. Через день он отвечает верно на десять вопросов, а на одиннадцатый строит запрос к огромной сырой таблице без LIMIT. База не падает, но отчет зависает, дашборд тормозит, а причина видна только в логах. Такие сбои проще предупредить, чем потом искать вручную.
Короткий чек-лист перед запуском
Перед первым запуском проверьте не промпт, а доступы и пределы. Если их нет, агент быстро превращается в обычный источник проблем: лишние данные, тяжелые запросы и долгие разборы после сбоя.
Достаточно пройти пять пунктов:
- У агента есть отдельная роль только для чтения. Она видит не всю базу, а только нужные таблицы или, лучше, подготовленные вьюхи для аналитики.
- Данные с PII закрыты по умолчанию. Имена, телефоны, ИИН, адреса и другие чувствительные поля открывают только под конкретный сценарий.
- Каждый запрос получает жесткий LIMIT и таймаут.
- Логи сохраняют исходный вопрос, сгенерированный SQL, время выполнения и текст ошибки.
- В команде заранее назначен человек или дежурная роль, которая отключает доступ при сбое.
Полезно проверить это на простом тесте. Пусть агент ответит на три обычных вопроса, один вопрос с попыткой запросить лишние поля и один тяжелый запрос без фильтра. Если он везде остается в рамках, режет объем ответа и пишет понятные логи, запуск уже выглядит разумно.
Если хотя бы один пункт пока не закрыт, не пускайте агента в продакшен. Сначала сузьте доступ, включите лимиты и договоритесь о реакции на сбой. Это скучная часть работы, но именно она потом экономит часы и нервы.
Что делать дальше
Не открывайте агенту всю базу и все типы вопросов сразу. Рабочий путь проще: взять один сценарий, довести его до спокойной работы, потом добавить следующий. Так легче понять, где ломается логика, где запросы выходят за рамки и где людям не хватает данных.
Обычно порядок такой: сначала дайте доступ к 1-2 безопасным витринам или представлениям, потом проверьте 20-30 реальных вопросов от аналитиков, после этого добавьте еще один сценарий, и только затем расширяйте allowlist и набор таблиц.
Такой запуск выглядит менее эффектно, чем большой старт, но почти всегда надежнее. Если агент уверенно отвечает на вопросы по продажам за день или по статусам заказов, это уже рабочий результат. Не нужно в первый же день пускать его в финансы, персональные данные и сырые таблицы.
Отдельно проверьте, где нужен маскинг PII и кто читает аудит-логи. ИИН, телефон, email, адрес, номер договора - все это лучше закрыть заранее, а не после первого спорного отчета. Полезно сразу договориться, какой след остается после каждого запроса: кто его запустил, к какой таблице обратился, сколько строк получил и сколько времени занял запрос.
Если лимиты и логи живут в разных местах, команда быстро теряет контроль. Удобнее, когда доступ к моделям, аудит и ограничения на уровне ключа собраны в одном контуре, а правила для базы остаются в самой СУБД. Для такой схемы AI Router на airouter.kz может быть полезен как единый LLM API-шлюз: через него проще централизовать аудит, rate limits и работу с моделями, но readonly, allowlist и SQL-лимиты все равно нужно настраивать рядом с базой.
Идея безопасного SQL-агента держится не на одном readonly-пользователе. Она держится на дисциплине: маленький охват на старте, понятный журнал действий, строгие лимиты и медленное расширение доступа. Такой подход редко выглядит впечатляюще на демо, зато не создает лишних проблем в проде.