FiveM SQL Error และ Slow Query แก้อย่างไร? วิธีแก้ Database Error และ Query ช้าใน oxmysql

ปัญหา FiveM SQL Error และ Slow Query ต้องแยกออกเป็น 2 กลุ่มใหญ่ คือ Query ผิดจน Database ปฏิเสธการทำงาน กับ Query ทำงานได้แต่ช้าเกินไป เพราะแนวทางแก้ต่างกันโดยสิ้นเชิง

ตัวอย่าง SQL Error ที่พบบ่อย

Unknown column
Table doesn't exist
Duplicate entry
SQL syntax error
Data too long
Column cannot be null
Lock wait timeout
Deadlock

ส่วนปัญหา Slow Query มักเกี่ยวกับ

ไม่มี Index
Query Rows มากเกินไป
SELECT *
N+1 Query
Query ใน Loop
JOIN ไม่เหมาะสม
ORDER BY / GROUP BY หนัก
LIKE '%keyword%'
Transaction ค้างนาน
Lock
Database Load
Server Hitch

ลำดับ Debug ที่เหมาะคือ

SQL Error?
↓
แก้ Error ก่อน
↓
Query ทำงานแล้ว?
↓
วัดเวลา
↓
ดู Slow Query
↓
EXPLAIN
↓
ตรวจ Index
↓
ลด Rows / Queries
↓
ตรวจ Lock / Transaction
↓
วัดผลอีกครั้ง

อย่าแก้ Query ช้าด้วยการเพิ่ม RAM หรือ CPU ก่อนตรวจ SQL และ Index เพราะ Query ที่ออกแบบผิดสามารถช้าได้แม้ Database Server แรงมาก

① FiveM SQL Error คืออะไร

SQL Error คือ Database ไม่สามารถ Execute คำสั่งตามที่ Resource ส่งมาได้

ตัวอย่าง

SELECT money
FROM characters
WHERE character_id = ?;

ถ้า Table ไม่มี money

อาจพบ Error

Unknown column 'money'

หรือถ้า Table characters ไม่มี

อาจพบ

Table 'database.characters' doesn't exist

ตรงนี้ปัญหาไม่ใช่ oxmysql ต่อ Database ไม่ได้

แต่เป็น Schema หรือ SQL Query ไม่ตรงกัน

② Slow Query คืออะไร

Slow Query คือ Query ที่ Execute สำเร็จแต่ใช้เวลานานเกิน Threshold

oxmysql ปัจจุบันมีค่า

mysql_slow_query_warning

สำหรับแจ้งเตือน Query ที่ใช้เวลานาน

ตัวอย่าง Configuration

set mysql_slow_query_warning 150

หมายถึงให้ oxmysql แจ้ง Query ที่ช้าเกิน Threshold ที่กำหนด

แต่ Warning ไม่ได้แปลว่า Database มีปัญหาเสมอไป เพราะ Server Hitch หรือ Startup Load ก็มีผลต่อเวลาที่รายงานได้

③ SQL Error กับ Slow Query ต่างกันอย่างไร

SQL Error
→ Query ทำงานไม่สำเร็จ

เช่น

Unknown column
Duplicate entry
Syntax error

ส่วน

Slow Query
→ Query สำเร็จ
→ แต่ใช้เวลานาน

เช่น

SELECT vehicles
→ 800 ms

อย่าเอาวิธีแก้ของสองกลุ่มมาปนกัน

④ จุดแรกที่ต้องดูคือ FXServer Console

เมื่อ oxmysql พบ Error มักมีรายละเอียด Query และ Database Error

เช่น

Unknown column 'garage' in 'field list'

ให้ดู

Resource ไหนยิง Query?
SQL อะไร?
Table ไหน?
Column ไหน?
Error Code อะไร?

อย่าแก้ Query อื่นที่ไม่เกี่ยวข้อง

⑤ เปิด mysql_debug ได้ไหม

ได้ในช่วง Debug

ตัวอย่าง

set mysql_debug true

oxmysql สามารถแสดง Query ใน Console เพื่อช่วยตรวจว่า Resource ส่ง SQL อะไรจริง

แต่ Production Server ไม่จำเป็นต้องเปิด Query Debug จำนวนมากตลอดเวลา

เพราะจะสร้าง Log Noise และข้อมูลบางอย่างอาจมีความอ่อนไหว

⑥ Unknown column คืออะไร

MariaDB Error Code 1054 หมายถึง

Unknown column

ตัวอย่าง

SELECT garage
FROM vehicles;

แต่ Table จริงมีเพียง

id
plate
model
stored

Database จึงหา garage ไม่เจอ

⑦ Unknown column แก้อย่างไร

ตรวจ

DESCRIBE vehicles;

หรือ

SHOW CREATE TABLE vehicles;

แล้วเปรียบเทียบกับ Query ที่ Resource ใช้

สาเหตุพบบ่อยคือ

SQL Migration ยังไม่ Import
Script ใหม่กว่า Database Schema
Database Schema ใหม่กว่า Script
สะกด Column ผิด
เลือก Database ผิด

⑧ อย่าเพิ่ม Column แบบเดาสุ่ม

ถ้า Resource แจ้ง

Unknown column 'garage'

ไม่ควรรีบทำ

ALTER TABLE vehicles
ADD garage VARCHAR(100);

โดยยังไม่อ่าน SQL/Migration ของ Resource

เพราะ Column อาจต้องเป็น Type อื่น หรือมี Index/Default/Constraint เพิ่ม

ควรใช้ Migration ที่ตรง Version

⑨ Table doesn't exist คืออะไร

MariaDB Error 1146

Table doesn't exist

ตัวอย่าง

SELECT *
FROM owned_vehicles;

แต่ Database ไม่มี Table owned_vehicles

ปัญหานี้มักเกิดจาก

ยังไม่ได้ Import SQL
ใช้ Framework คนละ Version
เลือก Database ผิด
Resource ต้อง Migration เพิ่ม

⑩ วิธีตรวจ Table

ใช้

SHOW TABLES;

หรือ

SHOW TABLES LIKE 'owned_vehicles';

ถ้าไม่มีจริง ให้หา SQL Installation/Migration ของ Resource

อย่าสร้าง Table เปล่าขึ้นมาเพียงเพื่อให้ Error หาย

⑪ SQL Syntax Error คืออะไร

MariaDB Error กลุ่ม 1064 มักเกิดจาก SQL Syntax ไม่ถูกต้อง

เช่น

SELECT *
FROM vehicles
WHER id = 1;

คำว่า

WHER

ผิด

ต้องเป็น

WHERE

⑫ Syntax Error อาจมาจาก Reserved Word

เช่นตั้ง Column หรือ Table ด้วยคำที่ Database Version มองเป็น Reserved Keyword

ตัวอย่างแนวคิด

SELECT group
FROM players;

อาจต้องใช้ชื่ออื่นหรือ Quote Identifier อย่างถูกต้อง

เช่น

SELECT `group`
FROM players;

แต่ในระยะยาวการใช้ชื่อที่ไม่ชน Reserved Keywords มัก Maintain ง่ายกว่า

⑬ SQL_MODE ก็ทำให้ Syntax ต่างกันได้

SQL บางแบบทำงานภายใต้ SQL Mode หนึ่ง แต่ไม่ทำงานในอีก Mode

ดังนั้น Script เก่าที่เคยทำงานบน Database รุ่นเก่าอาจ Error หลังย้าย Server

ตรวจ

SELECT @@sql_mode;

แต่ไม่ควรปิด Strict Mode ทั้ง Server เพียงเพื่อให้ Script เก่าใช้ได้โดยไม่ตรวจผลกระทบ

แก้ Schema/SQL ให้ถูกต้องมักเป็นแนวทางที่ดีกว่า

⑭ Duplicate entry คืออะไร

MariaDB Error 1062

Duplicate entry

เกิดเมื่อพยายาม Insert ค่าซ้ำเข้า Column ที่มี

PRIMARY KEY
หรือ
UNIQUE INDEX

ตัวอย่าง

plate = ABC123

มีอยู่แล้ว

แล้ว Script พยายาม Insert ABC123 ซ้ำใน Column ที่ Unique

⑮ ตัวอย่าง Duplicate Entry

Table

CREATE TABLE vehicles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    plate VARCHAR(20) UNIQUE
);

จากนั้น

INSERT INTO vehicles (plate)
VALUES ('ABC123');

ครั้งแรกผ่าน

แต่ทำซ้ำ

INSERT INTO vehicles (plate)
VALUES ('ABC123');

จะเกิด Duplicate Entry

⑯ Duplicate Entry แก้อย่างไร

ต้องถามก่อนว่า ข้อมูลควร Unique จริงหรือไม่

ถ้า Plate ต้องไม่ซ้ำ

ปัญหาอยู่ที่ Application Logic

Server ควรตรวจ/สร้าง Plate ใหม่

ไม่ใช่ลบ Unique Index ออกเพียงเพื่อให้ Error หาย

⑰ INSERT IGNORE ควรใช้ไหม

MariaDB รองรับ

INSERT IGNORE

ซึ่งสามารถเปลี่ยน Duplicate บางกรณีเป็น Warning

แต่ไม่ควรใช้เพื่อซ่อน Bug

ถ้าระบบต้องรู้ว่า Vehicle ถูก Insert หรือไม่ ควรจัด Duplicate Logic ให้ชัดเจน

⑱ ON DUPLICATE KEY UPDATE ใช้เมื่อไร

เหมาะเมื่อเจตนาคือ

ไม่มี Row
→ Insert

มี Row อยู่แล้ว
→ Update

ตัวอย่าง

INSERT INTO settings (
    character_id,
    volume
)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE
volume = VALUES(volume);

เหมาะกับ Setting หรือ Upsert Pattern

ไม่เหมาะกับข้อมูลที่ Duplicate แสดงถึง Bug

⑲ Column cannot be null คืออะไร

เกิดเมื่อ Column ถูกกำหนด

NOT NULL

แต่ Script ส่ง NULL

ตัวอย่าง

INSERT INTO characters (
    firstname
)
VALUES (NULL);

ถ้า firstname ห้าม Null Database จะปฏิเสธ

⑳ แก้ NULL Error อย่างไร

ตรวจว่า

ค่าหายจากไหน?
Client ไม่ส่ง?
Server Validation ไม่ครบ?
Framework API คืน nil?
Database Default ไม่มี?

อย่าแก้ด้วยการเปลี่ยนทุก Column ให้ NULL ได้

ถ้า Field จำเป็นจริงก็ควรบังคับให้ Application ส่งข้อมูลที่ถูกต้อง

㉑ Field doesn't have a default value

เกิดเมื่อ Insert ไม่ได้ส่ง Column ที่

NOT NULL

และไม่มี Default

เช่น Table

cash INT NOT NULL

แต่ Insert

INSERT INTO characters (
    firstname
)
VALUES (?);

โดยไม่มี cash

อาจ Error ตาม SQL Mode

㉒ วิธีแก้ Default Value

เลือกตาม Domain

เช่น Cash เริ่มต้นเป็น 0

cash INT NOT NULL DEFAULT 0

หรือให้ Application ส่งค่าเริ่มต้นอย่างชัดเจน

อย่ากำหนด Default แบบสุ่มให้ Field ที่มี Business Meaning สำคัญ

㉓ Data too long คืออะไร

เกิดเมื่อข้อมูลยาวเกิน Column ที่รองรับ

เช่น

name VARCHAR(20)

แต่ Script ส่ง String ยาว 100 ตัว

หรือ JSON ใหญ่เกิน Type ที่กำหนด

ควรตรวจ

Data Type
Maximum Length
Application Validation
Real-world Requirements

㉔ อย่าเพิ่ม VARCHAR เป็น 10000 ทุก Column

แม้ดูเหมือนแก้ง่าย แต่ Database Schema ควรเหมาะกับข้อมูลจริง

เช่น

Plate
→ VARCHAR(20)

Character name
→ VARCHAR(50)

JSON metadata
→ JSON/LONGTEXT ตาม Compatibility

เลือก Type ให้ตรง Use Case

㉕ Incorrect value / Data Type Error

ตัวอย่าง Script ส่ง

abc

เข้า Column

INT

หรือ Timestamp Format ผิด

ให้ตรวจ Lua/JS Parameter ก่อน Query

Parameterized Query ป้องกัน SQL Injection แต่ไม่ได้แปลง Business Data ให้ถูก Type ทุกกรณี

㉖ Foreign Key Error คืออะไร

ถ้า Schema ใช้ Foreign Key

เช่น

vehicles.character_id
→ characters.id

แล้ว Insert Vehicle ให้ Character ID ที่ไม่มีจริง

Database สามารถปฏิเสธได้

นี่เป็นเรื่อง Data Integrity ไม่ใช่ oxmysql Error

㉗ Foreign Key ดีไหม

มีข้อดีด้าน Referential Integrity

แต่ FiveM Schemas หลาย Framework ไม่ได้ใช้ Foreign Keys หนักทุกส่วน

ถ้าใช้ต้องเข้าใจ

Delete behavior
Cascade
Indexes
Migration
Transaction

ไม่ควรเพิ่มเพียงเพราะ Database Design ดูเป็นมาตรฐาน

㉘ Lock wait timeout คืออะไร

MariaDB Error 1205

Lock wait timeout exceeded

หมายถึง Transaction รอ Lock นานเกินกำหนด

อาจเกิดเมื่อ Transaction อื่นถือ Row/Table Lock ไว้นาน

ต้องดู

Transaction ไหนเปิดอยู่?
Query ไหน Lock Rows?
Transaction ยาวเกินไปหรือไม่?

㉙ Deadlock คืออะไร

MariaDB Error 1213

Deadlock found when trying to get lock

ตัวอย่าง

Transaction A
ล็อก Row 1
แล้วรอ Row 2

Transaction B
ล็อก Row 2
แล้วรอ Row 1

ทั้งสองรอกันเอง

Database ต้องเลือก Transaction หนึ่งให้ Fail เพื่อแก้ Deadlock

㉚ Deadlock แปลว่า Database พังไหม

ไม่

Deadlock เป็นสถานการณ์ที่เกิดขึ้นได้ในระบบ Concurrent

สิ่งสำคัญคือ Application ควร

Transaction สั้น
Lock Rows ในลำดับคงที่
Retry Operation ที่เหมาะสม

และไม่ทำ Work ที่ไม่เกี่ยวข้องค้างอยู่ใน Transaction นาน ๆ

㉛ Transaction ไม่ควรเปิดไว้นาน

ตัวอย่างไม่ดี

BEGIN
↓
Update player
↓
รอ HTTP API
↓
Wait 5 วินาที
↓
Update business
↓
COMMIT

ระหว่างนั้น Locks อาจถูกถือไว้นาน

ควรเตรียมข้อมูลที่จำเป็นก่อน แล้วทำ Transaction ให้สั้นที่สุด

㉜ Slow Query Warning ใน oxmysql

oxmysql มี Threshold ปัจจุบันผ่าน Convar

set mysql_slow_query_warning 150

Source ปัจจุบันกำหนด Default เป็นประมาณ

200 ms

ถ้าไม่ได้ Override

จึงสามารถใช้ Warning เป็นสัญญาณว่า Query ใดควรตรวจเพิ่มเติม

㉝ Slow Query Warning เชื่อได้ 100% ไหม

ไม่

เอกสาร oxmysql ระบุว่า Query Time อาจไม่แม่นทั้งหมด และ Server Hitch สามารถทำให้ Query ดูช้า

Startup Query ที่ช้าครั้งเดียวก็อาจไม่ใช่ปัญหาสำคัญ

ดังนั้นต้องดู Pattern

ช้าบ่อยไหม?
เกิดตอนผู้เล่นเยอะไหม?
Rows examined เท่าไร?
Index ใช้ไหม?

㉞ Slow Query เริ่มแก้จากอะไร

อย่าเริ่มด้วยการเขียน Query ใหม่ทันที

ใช้

EXPLAIN
SELECT ...

ก่อน

เช่น

EXPLAIN
SELECT id, plate
FROM vehicles
WHERE character_id = 100;

Database จะบอก Execution Plan ว่ากำลังเข้าถึง Table อย่างไร

㉟ EXPLAIN ใช้ดูอะไร

สิ่งที่ควรสนใจ เช่น

type
possible_keys
key
rows
Extra

ถ้า Query ตรวจ Rows จำนวนมากทั้ง Table และไม่มี Key ถูกใช้ นั่นเป็นสัญญาณให้ตรวจ Index

㊱ Full Table Scan คืออะไร

สมมติ Table มี

1,000,000 rows

แต่ Query ต้องการรถของ Character คนเดียว

SELECT *
FROM vehicles
WHERE character_id = ?;

ถ้าไม่มี Index ที่เหมาะ Database อาจต้องตรวจ Rows จำนวนมาก

นี่คือหนึ่งในสาเหตุ Query ช้าที่พบบ่อย

㊲ Index คืออะไร

Index คือโครงสร้างที่ช่วย Database หา Rows ตาม Column ได้เร็วขึ้น

เช่น Query หลักคือ

SELECT id, plate, model
FROM vehicles
WHERE character_id = ?;

อาจพิจารณา Index

CREATE INDEX idx_vehicles_character_id
ON vehicles(character_id);

แต่ต้องวัดและตรวจ EXPLAIN

㊳ อย่าสร้าง Index ทุก Column

MariaDB แนะนำให้ Index ตาม Query Patterns ที่ Application ใช้จริง

เพราะ Index เพิ่ม Cost ให้

INSERT
UPDATE
DELETE
Storage

ยิ่งมี Index เยอะ Write Operations ก็ยิ่งต้อง Update Structures หลายชุด

㊴ Composite Index คืออะไร

คือ Index หลาย Columns

เช่น Query

SELECT id
FROM vehicles
WHERE character_id = ?
  AND stored = ?;

อาจพิจารณา

CREATE INDEX idx_vehicle_owner_stored
ON vehicles(character_id, stored);

แต่ Order ของ Columns สำคัญและต้องอิง Query จริง

㊵ Index Column Boolean อย่างเดียวดีไหม

เช่น

stored = 0/1

มีค่าเพียงไม่กี่แบบ

Index stored เพียงตัวเดียวอาจ Selectivity ต่ำมาก

แต่

(character_id, stored)

อาจเหมาะกว่าใน Query ของ Garage

ต้องตรวจ EXPLAIN ไม่ใช่เดา

㊶ SELECT * ทำให้ Query ช้าไหม

ไม่เสมอ แต่สามารถส่งข้อมูลมากเกินจำเป็น

ถ้าต้องใช้เพียง

id
plate
stored

ควรเขียน

SELECT id, plate, stored
FROM vehicles
WHERE character_id = ?;

แทน

SELECT *

โดยเฉพาะ Table ที่มี JSON/LONGTEXT ขนาดใหญ่

㊷ LIMIT ใช้ช่วยได้ไหม

ถ้าต้องการเพียง Row เดียว

SELECT id, cash
FROM characters
WHERE id = ?
LIMIT 1;

ทำ Intent ชัดเจน

แต่ LIMIT 1 ไม่ได้แทน Index

ถ้า WHERE ไม่มี Index Database อาจยังตรวจข้อมูลจำนวนมากก่อนพบ Row

㊸ N+1 Query คืออะไร

ตัวอย่าง

Query 1
โหลดผู้เล่น 100 คน

จากนั้น
Query 100 ครั้ง
โหลดรถทีละคน

รวมเป็น 101 Queries

อาจแก้ด้วย

JOIN
IN (...)
Batch Query
Cache

ตาม Architecture

㊹ ตัวอย่าง N+1 ที่ควรหลีกเลี่ยง

for i = 1, #players do

    local vehicles =
        MySQL.query.await(
            'SELECT * FROM vehicles WHERE character_id = ?',
            {
                players[i].id
            }
        )
end

Player 200 คนก็อาจเกิด 200 Queries

ถ้าต้องใช้พร้อมกันสามารถออกแบบ Batch Query แทน

㊺ Query ใน Loop อันตรายเสมอไหม

ไม่เสมอ

Loop 3 ครั้งตอน Admin Command อาจไม่มีปัญหา

แต่

200 Players
×
ทุก 1 วินาที

ต่างกันมาก

ต้องวัด

Frequency
Rows
Concurrent Players
Query Cost

ก่อนตัดสิน

㊻ อย่า Query Database ทุก Frame

ไม่ควรทำ

CreateThread(function()

    while true do

        Wait(0)

        MySQL.query.await(
            'SELECT * FROM characters'
        )

    end
end)

Database ไม่ใช่ระบบ Frame State

ใช้ Cache/Event-driven Logic แทน

㊼ Runtime Cache ช่วยอย่างไร

ตัวอย่าง

Player Login
↓
SELECT Character
↓
Cache[source]
↓
Gameplay อ่าน Cache
↓
Save เมื่อจำเป็น

ช่วยลด Database Reads ซ้ำ

แต่ต้องมี Save Strategy ที่ปลอดภัย

㊽ Cache ทุกอย่างดีไหม

ไม่

Cache มากเกินไปสร้างปัญหา

Stale Data
Memory Usage
Synchronization
Restart Recovery

ควร Cache สิ่งที่อ่านบ่อยและเปลี่ยนแบบควบคุมได้

Database ยังเป็น Persistent Source

㊾ LIKE '%word%' ทำไมช้า

MariaDB ระบุว่า Query แบบ

WHERE name LIKE '%police%'

มักใช้ Standard BTREE Index ได้ไม่ดี เพราะ Wildcard อยู่ด้านหน้า

จึงสามารถนำไปสู่ Full Table Scan

ถ้าระบบ Search ใหญ่มากควรพิจารณา

Full-text index
Search architecture
Prefix search

ตาม Requirement

㊿ ORDER BY ช้าแก้อย่างไร

ตัวอย่าง

SELECT *
FROM transactions
WHERE character_id = ?
ORDER BY created_at DESC
LIMIT 50;

หากใช้บ่อย อาจต้องพิจารณา Composite Index

เช่น Concept

(character_id, created_at)

แต่ต้องดู Execution Plan

อย่าเพิ่ม Index ตามตัวอย่างโดยไม่วัด Query ของจริง

51 GROUP BY ช้าได้ไหม

ได้

โดยเฉพาะ Dataset ใหญ่และไม่มี Index ที่ช่วย

EXPLAIN อาจแสดง

Using temporary
Using filesort

ในบาง Query Plans

ไม่ได้หมายความว่า Error แต่เป็นสัญญาณให้ตรวจ Cost และ Index

52 JOIN ช้าแก้อย่างไร

ตรวจว่า Columns ที่ใช้ Join มี Index เหมาะสม

เช่น

SELECT ...
FROM vehicles v
JOIN characters c
    ON c.id = v.character_id;

โดยทั่วไป

characters.id
→ Primary Key

และ

vehicles.character_id

ควรได้รับการพิจารณาตาม Query Patterns

53 Rows Examined สำคัญอย่างไร

Query ที่คืน 10 Rows

แต่ต้องตรวจ

500,000 Rows

เป็นสัญญาณว่ามีโอกาส Optimization สูง

MariaDB Slow Query Log สามารถบันทึกข้อมูลอย่าง

query_time
lock_time
rows_sent
rows_examined

ซึ่งมีประโยชน์มากในการหา Query ที่แพงจริง

54 MariaDB Slow Query Log ใช้ได้ไหม

ได้

MariaDB มี Slow Query Log ของตัวเอง นอกเหนือจาก Warning ของ oxmysql

ใช้เพื่อเก็บ Queries ที่ใช้เวลานานตาม long_query_time

และสามารถตั้งให้ Log Query ที่ไม่ใช้ Index ได้ตาม Configuration

เหมาะกับ Production Investigation ที่ต้องการภาพระดับ Database Server

55 Slow Query Log มีข้อมูล Sensitive ไหม

มีได้

MariaDB เตือนว่า SQL Logs อาจมีข้อมูลที่ละเอียดอ่อน เช่น Parameters หรือ Query Text

ดังนั้น Slow Query/General Query Logs ต้องถูกป้องกันเหมือนข้อมูล Server สำคัญ

อย่า Upload Log ทั้งไฟล์สู่ Public เพื่อขอความช่วยเหลือโดยไม่ลบข้อมูลสำคัญ

56 Prepared Query ช่วย Slow Query ไหม

บางกรณี

oxmysql มี MySQL.prepare

สำหรับ Query ที่ใช้บ่อย

แต่ถ้า Query ช้าเพราะ

ไม่มี Index
Scan 5 ล้าน Rows
JOIN ผิด

เปลี่ยนจาก query เป็น prepare อย่างเดียวไม่ได้แก้ Root Cause

57 Transaction ช่วย Performance ไหม

ใช้ Transaction เพื่อ Consistency เป็นหลัก

หลาย Writes ที่สัมพันธ์กันอาจได้ประโยชน์จากการจัด Transaction อย่างเหมาะสม

แต่ Transaction ที่ใหญ่และเปิดนานอาจเพิ่ม Lock Contention

ดังนั้น

Transaction
→ สั้น
→ เฉพาะ Operations ที่ต้อง Atomic

เป็นหลักที่ดี

58 วิธี Debug Slow Query แบบเร็วที่สุด

ใช้ Flow

① หา Query ที่ช้า
② Copy Query ไป Test Environment
③ ใส่ Parameters จริง
④ EXPLAIN
⑤ ดู Rows / Keys
⑥ ตรวจ Index
⑦ ลด Columns
⑧ ลด Rows
⑨ ลด Query Frequency
⑩ ทดสอบใหม่

อย่า Optimize จากความรู้สึก

วัดก่อนและหลังเสมอ

59 Checklist FiveM SQL Error และ Slow Query

ตรวจให้ครบ

① Error Code อะไร?
② Resource ไหนยิง Query?
③ SQL ตัวไหน?
④ Database Name ถูกไหม?
⑤ Table มีไหม?
⑥ Column มีไหม?
⑦ Schema Version ตรงไหม?
⑧ Migration Import แล้วไหม?
⑨ SQL Syntax ถูกไหม?
⑩ Reserved Keyword หรือไม่?
⑪ SQL_MODE มีผลไหม?
⑫ Duplicate Key จริงหรือ Bug?
⑬ NULL ถูกต้องไหม?
⑭ Default Value มีไหม?
⑮ Data Type ตรงไหม?
⑯ Data Length พอไหม?
⑰ Foreign Key ถูกไหม?
⑱ Transaction ค้างหรือไม่?
⑲ Lock wait timeout หรือไม่?
⑳ Deadlock หรือไม่?
㉑ Slow Query Warning เกิดบ่อยไหม?
㉒ mysql_debug จำเป็นไหม?
㉓ EXPLAIN แล้วหรือยัง?
㉔ Query ใช้ Index ไหม?
㉕ Rows examined เท่าไร?
㉖ SELECT * จำเป็นไหม?
㉗ LIMIT เหมาะไหม?
㉘ Query อยู่ใน Loop หรือไม่?
㉙ มี N+1 Query หรือไม่?
㉚ Cache เหมาะไหม?
㉛ LIKE มี Leading Wildcard หรือไม่?
㉜ ORDER BY/GROUP BY หนักหรือไม่?
㉝ JOIN Columns มี Index ไหม?
㉞ Index เยอะเกินไปหรือไม่?
㉟ วัดผลหลังแก้แล้วหรือยัง?

⑥⓪ ตัวอย่างแก้ Slow Query ระบบ Garage

สมมติ Resource มี Query

SELECT *
FROM vehicles
WHERE character_id = ?;

Table มี

2,000,000 vehicles

และไม่มี Index บน

character_id

oxmysql เริ่มแจ้ง Slow Query

ขั้นที่ 1 — ดู Query

แทนที่จะเดา ให้รัน

EXPLAIN
SELECT *
FROM vehicles
WHERE character_id = 100;

ถ้าพบว่า Database ตรวจ Rows จำนวนมากและไม่มี Key ที่เหมาะ

ขั้นที่ 2 — ตรวจ Index

SHOW INDEX
FROM vehicles;

ถ้าไม่มี Index ที่ตรง Query Pattern

อาจพิจารณา

CREATE INDEX idx_vehicles_character_id
ON vehicles(character_id);

ขั้นที่ 3 — ลด Columns

ถ้า Garage List ใช้แค่

id
plate
model
stored

ปรับเป็น

SELECT
    id,
    plate,
    model,
    stored
FROM vehicles
WHERE character_id = ?;

ไม่ต้องโหลด JSON Vehicle Properties ขนาดใหญ่ทุกครั้ง

ขั้นที่ 4 — ทดสอบ EXPLAIN อีกครั้ง

EXPLAIN
SELECT
    id,
    plate,
    model,
    stored
FROM vehicles
WHERE character_id = 100;

ตรวจว่า Key ถูกใช้และ Rows Estimate ลดลงหรือไม่

ขั้นที่ 5 — วัด oxmysql Warning

Restart/Test Resource ตามความเหมาะสม แล้วดูว่า Query Time ลดลงจริงหรือไม่

นี่คือ Optimization จาก Evidence

ไม่ใช่เดา

ตัวอย่างแก้ N+1 Query

แบบเดิม

local players =
    MySQL.query.await(
        'SELECT id FROM characters WHERE online = 1'
    )

for i = 1, #players do

    players[i].vehicles =
        MySQL.query.await(
            'SELECT id, plate FROM vehicles WHERE character_id = ?',
            {
                players[i].id
            }
        )
end

ถ้ามี 100 Players

1 Query
+
100 Queries

อาจกลายเป็น 101 Queries

สามารถออกแบบใหม่ให้โหลด Vehicle Sets เป็น Batch แล้ว Group ฝั่ง Lua หรือใช้ JOIN ตาม Data Shape ที่ต้องการ

สิ่งสำคัญคือเลี่ยง Query จำนวนมากที่เกิดจาก Pattern โดยไม่จำเป็น

ตัวอย่าง Transaction ที่ควรใช้

ซื้อรถ

ต้องทำ

เงินผู้เล่นลด
+
Vehicle Record ถูกสร้าง

ถ้าลดเงินสำเร็จแต่ Insert รถ Fail

ข้อมูลเสีย

จึงควรใช้ Transaction ตาม Architecture

local success =
    MySQL.transaction.await({
        {
            [[
                UPDATE characters
                SET bank = bank - ?
                WHERE id = ?
                  AND bank >= ?
            ]],
            {
                price,
                characterId,
                price
            }
        },
        {
            [[
                INSERT INTO vehicles (
                    character_id,
                    plate,
                    model
                )
                VALUES (?, ?, ?)
            ]],
            {
                characterId,
                plate,
                model
            }
        }
    })

แต่ระบบ Production ต้องตรวจ Affected Rows และ Business State เพิ่ม ไม่ใช่ถือว่า Transaction อย่างเดียวทำให้ Economy ปลอดภัยทั้งหมด

ตัวอย่าง Duplicate Entry ที่ควรแก้ใน Logic

สมมติ Plate เป็น Unique

UNIQUE KEY unique_plate (plate)

ก่อน Insert Vehicle ควรสร้าง Plate ที่ Server ควบคุมและมีวิธีรับมือ Collision

ไม่ควรแก้ Error ด้วย

DROP UNIQUE INDEX

ถ้ากฎของระบบคือรถทุกคันต้องมี Plate ไม่ซ้ำ

Database Constraint กำลังช่วยป้องกันข้อมูลเสีย

ตัวอย่าง Deadlock ในระบบโอนเงิน

สมมติ Transaction A

Lock Account 1
↓
Lock Account 2

แต่ Transaction B

Lock Account 2
↓
Lock Account 1

มีโอกาส Deadlock

การกำหนด Lock/Update Order ให้สม่ำเสมอ เช่นเรียง Account ID ก่อนเสมอ สามารถลด Deadlock Pattern บางประเภทได้

และ Operation ที่เหมาะควรสามารถ Retry เมื่อพบ Deadlock ตามการออกแบบ

Slow Query ไม่ได้แก้ด้วย Index อย่างเดียว

บาง Query ช้าเพราะ

เรียกบ่อยเกินไป

เช่น Query 5 ms

ดูเร็วมาก

แต่เรียก

5,000 ครั้ง/วินาที

ก็เป็น Load ใหญ่

Performance ต้องดู

Cost ต่อ Query
×
Frequency
×
Concurrent Players

เสมอ

Query 300 ms ครั้งเดียวกับ 10 ms 1,000 ครั้ง อะไรแย่กว่า

ต้องดู Workload จริง

Query 300 ms ที่รันตอน Server Startup วันละครั้งอาจแทบไม่มีผลกับ Player

แต่ Query 10 ms ที่รันทุก Player ทุกวินาทีอาจสร้าง Load มากกว่า

จึงเป็นเหตุผลที่ oxmysql Documentation ระบุว่า Slow Query Warning โดยเฉพาะช่วง Startup ไม่จำเป็นต้องเป็นปัญหาเสมอไป

คำถามที่พบบ่อยเกี่ยวกับ FiveM SQL Error และ Slow Query

FiveM Unknown column แก้อย่างไร

ตรวจ Table Schema และ Migration ว่า Column ที่ Query ใช้มีจริงหรือไม่

Table doesn't exist แก้อย่างไร

Import/Create Schema ที่ Resource ต้องการและตรวจว่ากำลังเชื่อม Database ถูกตัว

Duplicate entry แก้อย่างไร

ตรวจ Unique/Primary Key และแก้ Logic ที่พยายาม Insert Duplicate หากข้อมูลควร Unique จริง

SQL Syntax Error แก้อย่างไร

อ่านตำแหน่ง Error ตรวจ Syntax, Reserved Keyword และ SQL_MODE/Database Version

Column cannot be null คืออะไร

Script ส่ง NULL เข้า Column ที่กำหนด NOT NULL

Data too long คืออะไร

ข้อมูลยาวเกิน Capacity ของ Column

Lock wait timeout คืออะไร

Transaction รอ Lock เกิน Timeout

Deadlock คืออะไร

Transactions หลายตัวถือ Lock และรอกันจน Database ต้องยกเลิกบาง Transaction

Slow Query ใน oxmysql คืออะไร

Query ที่ใช้เวลานานเกิน mysql_slow_query_warning

Default Threshold oxmysql เท่าไร

Source ปัจจุบันกำหนด Default ที่ประมาณ 200 milliseconds หากไม่ได้ Override

ตั้ง Slow Query Warning เองได้ไหม

ได้ เช่น

set mysql_slow_query_warning 150

Slow Query Warning แปลว่า Database เสียไหม

ไม่ Server Hitch หรือ Startup Load ก็ทำให้ Timing สูงได้

EXPLAIN คืออะไร

ใช้ดู Execution Plan ว่า Database จะอ่าน Query อย่างไรและใช้ Index ใด

Index ช่วยอะไร

ช่วยลดการค้นหา Rows สำหรับ Query Pattern ที่เหมาะสม

ควร Index ทุก Column ไหม

ไม่ Extra Index เพิ่ม Storage และทำให้ Writes แพงขึ้น

SELECT * ไม่ดีเสมอไปไหม

ไม่ แต่ควรเลือกเฉพาะ Columns ที่ต้องใช้เมื่อ Table มีข้อมูลมากหรือ Columns ใหญ่

Query ทุก Tick ได้ไหม

ไม่ควร

N+1 Query คืออะไร

Pattern ที่โหลดรายการหนึ่งครั้งแล้ว Query แยกเพิ่มทีละ Record จนเกิด Query จำนวนมาก

LIKE '%keyword%' ช้าทำไม

Leading Wildcard มักทำให้ Standard BTREE Index ใช้ได้ไม่เต็มประสิทธิภาพและอาจเกิด Full Scan

Prepared Query แก้ Slow Query ได้ทุกกรณีไหม

ไม่ ถ้า Root Cause คือ Index หรือ Query Design ผิดก็ยังต้องแก้ตรงนั้น

Transaction ช่วยอะไร

ช่วยให้หลาย Database Operations สำเร็จหรือล้มเหลวเป็นชุดตาม Transaction Logic

Transaction ยิ่งใหญ่ยิ่งดีไหม

ไม่ Transaction ยาวสามารถถือ Locks นานและเพิ่ม Contention

MariaDB Slow Query Log ใช้ได้ไหม

ได้ และสามารถเก็บ Query Time, Lock Time, Rows Sent และ Rows Examined เพื่อวิเคราะห์ได้

mysql_debug เปิด Production ได้ไหม

เปิดได้แต่ควรใช้เท่าที่จำเป็น เนื่องจาก Query Logging จำนวนมากสร้าง Noise และอาจมีข้อมูลสำคัญ

สรุป FiveM SQL Error และ Slow Query แก้อย่างไร

วิธีแก้ที่ถูกต้องคือเริ่มจากแยกก่อนว่า

Query พัง
หรือ
Query ช้า

ถ้า Query พัง ให้ตรวจ

Table
Column
Schema
Syntax
Data Type
Unique Constraint
NULL
Transaction

ถ้า Query ทำงานแต่ช้า ให้ใช้

Slow Query Warning
↓
EXPLAIN
↓
Index
↓
Rows Examined
↓
Query Frequency
↓
Cache / Batch
↓
Measure Again

ตัวอย่างการตั้งค่า oxmysql

set mysql_slow_query_warning 150

และเมื่อหา Query ช้าเจอ

EXPLAIN
SELECT
    id,
    plate,
    model
FROM vehicles
WHERE character_id = ?;

จากนั้นจึงพิจารณา Index ที่ตรง Query Pattern

CREATE INDEX idx_vehicles_character_id
ON vehicles(character_id);

แต่ห้ามใช้แนวคิด

Query ช้า
↓
สร้าง Index ทุก Column

เพราะ Index ที่ไม่จำเป็นทำให้

INSERT
UPDATE
DELETE

แพงขึ้นและใช้ Storage เพิ่ม

สำหรับ Developer ที่เรียน FiveM กับ comsiam สิ่งสำคัญคือ อย่า Optimize จากความรู้สึก ให้หา Query ที่ช้าจริง ดู Execution Plan แล้ววัดผลก่อนและหลังแก้

หลักสำคัญจาก comsiam อีกข้อคือ Database Performance ไม่ได้ขึ้นกับ SQL หนึ่งบรรทัดอย่างเดียว แต่เป็น

Query Cost
×
จำนวนครั้งที่ Query
×
จำนวนผู้เล่น
×
จำนวน Rows

Query ที่เร็ว 5 ms อาจกลายเป็นปัญหาใหญ่ถ้ายิงหลายพันครั้งต่อวินาที ในขณะที่ Query 300 ms ที่รันเพียงครั้งตอน Startup อาจแทบไม่มีผลต่อ Gameplay

หัวข้อถัดไปคือ FiveM NUI คืออะไร ใช้ HTML CSS JavaScript อย่างไร ซึ่งจะเปลี่ยนจาก Database มาสู่ระบบ UI และอธิบายความสัมพันธ์ระหว่าง Lua/JavaScript ฝั่ง Client กับ Browser NUI, ui_page, files, SendNUIMessage, NUI Callback และ Focus แบบครบวงจร

Comments

Popular posts from this blog

FiveM ยังน่าเล่นไหม? Enhanced เปลี่ยน FiveM แค่ไหน

FiveM คืออะไร เล่นอย่างไร สำหรับมือใหม่ เริ่มต้นตั้งแต่ศูนย์

วิธีตั้ง Admin Permission ด้วย add_ace และ add_principal FiveM แบบละเอียด