Оптимизация MySQL-запросов в Pawn: Избавляемся от зависаний сервера
Серверный движок SA:MP/CRMP работает по строгому однопоточному принципу (Single-Threaded). Это значит, что абсолютно все процессы — передвижение игроков, работа таймеров, обработка команд, проверка античета и взаимодействие с файлами — выполняются последовательно в одном главном потоке. Если какая-то функция внутри вашего Pawn-скрипта занимает на выполнение 500 миллисекунд, весь сервер буквально замирает на полсекунды. Для игроков это выглядит как резкий скачок пинга, задержка анимаций или кратковременное зависание игры.
Самый частый виновник таких микрофризов и «зависаний» — это некорректная работа с базой данных MySQL. В этой статье мы разберем разницу между типами запросов в плагине BlueG MySQL и покажем, как один неоптимизированный запрос при авторизации может парализовать работу сервера.
Блокирующие запросы (mysql_query) — главная ошибка разработчика
В старых версиях плагина MySQL (и, к сожалению, во многих современных модах-самописах) для отправки любого SQL-запроса используется функция mysql_query(). Это так называемый синхронный (блокирующий) запрос.
Когда сервер выполняет mysql_query, происходит следующее:
- Главный поток сервера приостанавливает обработку игрового процесса.
- Сервер отправляет запрос через сеть на сервер MySQL хостинга.
- Сервер SA:MP ведет ожидания, пока MySQL найдет нужную строчку в таблице, обработает ее и пришлет ответ назад.
- Только после получения ответа главный поток «просыпается» и продолжает игру.
Пример катастрофы: Игрок заходит на сервер под ником Ivan_Petrov. Плагин отправляет синхронный запрос: mysql_query(db_handle, "SELECT * FROM users WHERE name = 'Ivan_Petrov'");. Если в таблице аккаунтов накопилось 50 000 записей, а колонка name не имеет индекса, базе данных потребуется около 200–400 миллисекунд, чтобы прочесть весь диск. Всё это время сервер будет полностью «мертв» для остальных 200 игроков, находящихся в онлайне.
Асинхронные запросы (mysql_tquery и mysql_pquery) — спасение для TPS
Современные версии плагина MySQL от BlueG (R39-R41+) предлагают мощный инструмент — асинхронность. Функции mysql_tquery (Threaded Query) и mysql_pquery (Parallel Query) выполняют работу в отдельных, параллельных потоках операционной системы Linux, вообще не затрагивая игровой движок.
При использовании асинхронного подхода алгоритм кардинально меняется:
- Главный поток вызывает
mysql_tquery, моментально передает задачу параллельному потоку плагина и сразу же продолжает обрабатывать игру (игроки бегают, урон регистрируется, TPS равен 50). - Параллельный поток в фоновом режиме связывается с базой данных, ждет ответа и обрабатывает тяжелый SQL-запрос.
- Когда данные получены, плагин MySQL подает сигнал главному потоку и вызывает специальную функцию ответа — колбэк (Callback), где мы уже безопасно выдаем игроку его уровень и деньги.
Практический пример: Перевод авторизации на асинхронные рельсы
Как делать НЕЛЬЗЯ (Синхронный блокирующий код):
public OnPlayerConnect(playerid)
{
new query[128], Cache:result;
GetPlayerName(playerid, query, MAX_PLAYER_NAME);
format(query, sizeof(query), "SELECT id FROM users WHERE name = '%s'", query);
result = mysql_query(db_handle, query);
if(cache_num_rows() > 0) {
ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Авторизация", "Введите пароль:", "Войти", "Выход");
} else {
ShowPlayerDialog(playerid, DIALOG_REG, DIALOG_STYLE_INPUT, "Регистрация", "Придумайте пароль:", "Далее", "Выход");
}
cache_delete(result);
return 1;
}
Как делать ПРАВИЛЬНО (Асинхронный код с Колбэком):
public OnPlayerConnect(playerid)
{
new query[128], p_name[MAX_PLAYER_NAME];
GetPlayerName(playerid, p_name, sizeof(p_name));
format(query, sizeof(query), "SELECT id FROM users WHERE name = '%s'", p_name);
mysql_tquery(db_handle, query, "CheckPlayerAccount", "d", playerid);
return 1;
}
forward CheckPlayerAccount(playerid);
public CheckPlayerAccount(playerid)
{
if(!IsPlayerConnected(playerid)) return 1;
if(cache_num_rows() > 0) {
ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Авторизация", "Введите пароль:", "Войти", "Выход");
} else {
ShowPlayerDialog(playerid, DIALOG_REG, DIALOG_STYLE_INPUT, "Регистрация", "Придумайте пароль:", "Далее", "Выход");
}
return 1;
}
В чем разница между mysql_tquery и mysql_pquery?
mysql_tquery: Создает один фоновый поток для запросов. Все асинхронные запросы встают в одну фоновую очередь и выполняются строго друг за другом. Это гарантирует, что если вы сначала отправили запрос на сохранение данных, а затем на их выгрузку, они выполнятся в правильном хронологическом порядке. Идеально подходит для 95% задач мода.mysql_pquery: Создает пул (несколько) параллельных потоков. Запросы выполняются одновременно на разных ядрах процессора. Это работает быстрее, но порядок выполнения не гарантирован. Используйтеmysql_pqueryтолько для тяжелых аналитических выборок или логов (например, запись лога убийств или действий администраторов), где последовательность выполнения не критична для логики аккаунта.
Золотое правило индексации баз данных
Даже асинхронный запрос может начать тормозить, если база данных настроена неверно. Если фоновый поток будет обрабатывать один SQL-запрос 5 секунд, игрок будет просто стоять на спавне и бесконечно ждать появления диалогового окна.
Чтобы этого не происходило, обязательно добавьте Индексы (INDEX / UNIQUE) на все поля, по которым вы делаете выборку (WHERE). В таблице пользователей индексы обязательно должны стоять на колонках name (никнейм) и id.
Команда для оптимизации через phpMyAdmin:
Откройте вкладку SQL в вашей базе данных на хостинге и выполните команду:ALTER TABLE `users` ADD INDEX (`name`);
Это ускорит поиск аккаунта при заходe игрока в сотни раз, снизив нагрузку на процессор хостинга до минимума.