Оптимізація 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`);
Це прискорить пошук акаунту при заході гравця в сотні разів, знизивши навантаження на процесор хостингу до мінімуму.