FiveM Database คืออะไร ใช้ทำอะไรในเซิร์ฟเวอร์
FiveM Database คือระบบจัดเก็บข้อมูลถาวรของเซิร์ฟเวอร์ เช่น ข้อมูลตัวละคร เงิน รถ Inventory บ้าน Job และข้อมูลผู้เล่นอื่น ๆ เพื่อให้ข้อมูลยังคงอยู่แม้ Resource หรือ FXServer ถูก Restart
ตัวอย่างข้อมูลที่มักเก็บใน Database
Player
├── identifier
├── character
├── cash
├── bank
├── job
├── inventory
├── vehicles
├── houses
└── settings
หากเก็บข้อมูลไว้เพียง Lua Table เช่น
local Players = {}
ข้อมูลนั้นเป็นเพียง Runtime Memory
เมื่อ Resource Restart
Resource Restart
↓
Lua Runtime ถูกสร้างใหม่
↓
Players = {}
↓
ข้อมูลเดิมหาย
แต่ถ้าบันทึกลง Database
FXServer
↓
Database Server
↓
Tables
↓
ข้อมูลยังอยู่
นี่คือเหตุผลที่ FiveM Server จริงแทบทุกระบบที่ต้องการ Persistence จำเป็นต้องมี Database หรือ Persistent Storage ที่เหมาะสม
สำหรับ FiveM ปัจจุบันจะพบการใช้งาน MySQL/MariaDB ร่วมกับ Resource เช่น oxmysql อยู่มาก โดย Script ฝั่ง Server เป็นผู้ส่ง SQL Query ไปยัง Database และนำผลลัพธ์กลับมาใช้กับ Gameplay
① FiveM Database คืออะไร
Database คือระบบที่ใช้จัดเก็บข้อมูลในรูปแบบที่สามารถ
Create
Read
Update
Delete
ได้อย่างเป็นระบบ
หรือเรียกว่า
CRUD
ตัวอย่าง
Create
→ สร้างตัวละคร
Read
→ โหลดข้อมูลตัวละคร
Update
→ เปลี่ยนเงิน/Job
Delete
→ ลบตัวละคร
Database จึงไม่ได้มีไว้แค่เก็บ Username แต่เป็น Source of Truth ของระบบจำนวนมาก
② FiveM มี Database ในตัวไหม
FiveM Core ไม่ได้บังคับให้ Resource ทุกตัวใช้ MySQL
Resource ง่าย ๆ เช่น
Command
Marker
HUD
Local Effect
อาจไม่ต้องใช้ Database เลย
แต่ Framework และ Roleplay Server ส่วนใหญ่ต้องมี Persistent Data จึงมักเชื่อมกับ Database ภายนอกผ่าน Resource/Library
③ FiveM Database เก็บอะไรได้บ้าง
ตัวอย่างที่พบบ่อย
Characters
Accounts
Money
Jobs
Grades
Inventory
Vehicles
Properties
Garages
Licenses
Phone Data
Businesses
Punishments
Settings
Logs
แต่ไม่ควรเก็บทุก Runtime State ลง Database เพียงเพราะทำได้
ต้องเลือกตามความจำเป็น
④ Persistence คืออะไร
Persistence หมายถึงข้อมูลยังอยู่หลัง Runtime จบ
ตัวอย่าง
ผู้เล่นมีเงิน
10,000
Player Disconnect
กลับเข้ามาอีกวัน
ยังต้องเป็น
10,000
นี่คือ Persistent Data
จึงควรเก็บลง Database
⑤ Runtime Memory ต่างจาก Database อย่างไร
Runtime Memory
local Cash = {
[1] = 10000
}
เร็วและเหมาะกับข้อมูลชั่วคราว
แต่เมื่อ Resource Restart ข้อมูลจะหาย
Database
players table
↓
cash = 10000
อยู่บน Database Server และยังคงอยู่ข้าม Resource Restart/FXServer Restart
⑥ Database กับ Lua Table ใช้แทนกันไหม
ไม่
ทั้งสองเหมาะคนละงาน
Lua Table เหมาะกับ
Cache
Current Sessions
Cooldowns
Temporary Missions
Active Lobbies
Runtime Objects
Database เหมาะกับ
Money
Characters
Vehicles
Properties
Long-term Player Data
Server ที่ดีมักใช้ทั้งสองอย่างร่วมกัน
⑦ ทำไมไม่ Query Database ทุกครั้ง
เพราะ Database Access มี Cost มากกว่าอ่าน Lua Table ใน Memory
ตัวอย่างไม่เหมาะ
ทุก Frame
↓
SELECT money
↓
Database
แบบนี้ไม่มีเหตุผล
Pattern ที่พบบ่อยคือ
Player Login
↓
Load Database
↓
เก็บ Runtime Player Object/Cache
↓
Gameplay ใช้ Cache
↓
Save เมื่อข้อมูลเปลี่ยนหรือช่วงที่เหมาะสม
⑧ Database เป็น Source of Truth เสมอไหม
ขึ้นกับ Architecture
สำหรับ Persistent Data โดยทั่วไป Database เป็น Durable Source of Truth
แต่ในช่วง Player Online Framework อาจมี Runtime Object เป็น State ปัจจุบัน เช่น
Database
↓
Player Login
↓
Runtime Player Object
↓
Gameplay Changes
↓
Save กลับ Database
ต้องออกแบบ Synchronization ให้ถูก
⑨ FiveM นิยมใช้ Database อะไร
FiveM Ecosystem มักพบ
MySQL
MariaDB
ร่วมกับ Connector Resource
เอกสาร oxmysql ปัจจุบันระบุว่ารองรับ MySQL 8 และแนะนำ MariaDB สำหรับ Compatibility กับ Resources FiveM จำนวนมาก
⑩ MariaDB คืออะไร
MariaDB เป็น Relational Database Management System ที่ใช้ SQL และมี Compatibility สูงกับ MySQL Ecosystem
FiveM Server จำนวนมากใช้ MariaDB เพราะ
ติดตั้งตรง ๆ ได้
รองรับ SQL
ใช้กับ Resource FiveM จำนวนมาก
Tools จัดการมีเยอะ
⑪ MySQL คืออะไร
MySQL ก็เป็น Relational Database Management System
สามารถสร้าง
Database
Tables
Rows
Columns
Indexes
Relationships
และใช้ SQL Query เพื่อจัดการข้อมูล
ตัวอย่าง
SELECT *
FROM users;
⑫ SQL คืออะไร
SQL ย่อมาจาก
Structured Query Language
ใช้สั่งงาน Relational Database
ตัวอย่างอ่านข้อมูล
SELECT *
FROM users
WHERE id = 1;
เพิ่มข้อมูล
INSERT INTO users (name)
VALUES ('Somchai');
แก้ไข
UPDATE users
SET cash = 5000
WHERE id = 1;
ลบ
DELETE FROM users
WHERE id = 1;
⑬ Database คืออะไรในเชิงโครงสร้าง
สมมติ Database ชื่อ
fivem
ภายในอาจมี Tables
fivem
├── users
├── characters
├── owned_vehicles
├── inventories
├── properties
└── transactions
แต่ละ Table เก็บข้อมูลแต่ละ Domain
⑭ Table คืออะไร
Table คล้ายตารางข้อมูล
ตัวอย่าง users
| id | identifier | name | cash |
|---|---|---|---|
| 1 | license:abc | Somchai | 5000 |
| 2 | license:def | Mana | 12000 |
แต่ละแถวคือ Record
แต่ละ Column คือ Attribute
⑮ Row คืออะไร
Row คือข้อมูลหนึ่ง Record
เช่น
id = 1
identifier = license:abc
name = Somchai
cash = 5000
ใน FiveM อาจแทน
Player
Character
Vehicle
Property
หนึ่งรายการ
⑯ Column คืออะไร
Column คือ Field ใน Table
เช่น
users
├── id
├── identifier
├── firstname
├── lastname
├── cash
└── bank
แต่ละ Column ควรมี Data Type ที่เหมาะสม
⑰ Primary Key คืออะไร
Primary Key ใช้ระบุ Row แบบ Unique
ตัวอย่าง
id INT UNSIGNED
AUTO_INCREMENT
PRIMARY KEY
ทำให้ทุก Record มี ID ของตัวเอง
เช่น
Vehicle ID = 521
สามารถใช้เป็น Persistent Identity ได้ดีกว่า Runtime Entity Handle หรือ Network ID
⑱ FiveM Player ID ควรเก็บ Database ไหม
source หรือ Server ID เช่น
1
17
63
เป็น Runtime Session ID
ไม่ควรใช้เป็น Permanent Database Identity
Player คนเดิมเข้าใหม่อาจได้ Server ID ใหม่
ต้องใช้ Identifier/Character ID ที่เหมาะกับ Architecture แทน
⑲ Network ID ควรเก็บ Database ไหม
ไม่ควรใช้เป็น Permanent Vehicle ID
Network ID เป็น Runtime Network Entity Reference และสามารถถูก Reuse หลัง Entity Lifetime จบ
Database ควรเก็บ
vehicle_id
plate
model
properties
owner_character_id
แล้วสร้าง Runtime Entity ใหม่เมื่อจำเป็น
⑳ Entity Handle เก็บ Database ได้ไหม
ไม่ควร
เช่น
vehicle handle = 128557
เป็น Runtime Reference
หลัง Restart ไม่มีความหมายใน Database
จึงต้องแยก
Database Identity
ออกจาก
Runtime Entity Identity
㉑ Identifier คืออะไร
FiveM สามารถมี Player Identifiers หลายประเภทตาม Environment/Configuration
Framework มักเลือก Identifier ที่เหมาะสำหรับเชื่อม Player กับ Database
แต่ Database Design ที่ซับซ้อนควรแยก
Account/User
ออกจาก
Character
อย่างชัดเจน
㉒ Character ID สำคัญอย่างไร
สมมติหนึ่ง Account มี 3 Characters
Account
├── Character 101
├── Character 102
└── Character 103
Garage และ Inventory ควรอ้าง Character ที่ถูกต้อง
ไม่ใช่เอาทุกอย่างผูกกับ Session source
㉓ ตัวอย่าง Character Table
CREATE TABLE characters (
id INT UNSIGNED
AUTO_INCREMENT
PRIMARY KEY,
identifier VARCHAR(100)
NOT NULL,
firstname VARCHAR(50)
NOT NULL,
lastname VARCHAR(50)
NOT NULL,
cash INT NOT NULL
DEFAULT 0
);
นี่เป็นเพียงตัวอย่างพื้นฐาน
Production Schema ต้องออกแบบตาม Framework และ Requirements จริง
㉔ Vehicle Table ตัวอย่าง
CREATE TABLE vehicles (
id INT UNSIGNED
AUTO_INCREMENT
PRIMARY KEY,
character_id INT UNSIGNED
NOT NULL,
plate VARCHAR(20)
NOT NULL,
model VARCHAR(100)
NOT NULL,
garage VARCHAR(100),
stored TINYINT
NOT NULL
DEFAULT 1
);
จากนั้น Vehicle Runtime สามารถเชื่อมกลับด้วย
vehicle.id
แทน Net ID
㉕ Inventory เก็บ Database อย่างไร
มีหลาย Architecture
หนึ่งแบบคือ Row ต่อ Item
inventory_items
├── owner_id
├── slot
├── item
├── count
└── metadata
อีกแบบอาจเก็บ JSON ตาม Framework
แต่ Database Design มีผลมากต่อ
Query Performance
Search
Indexing
Migration
จึงไม่ควรเลือกเพียงเพราะเขียนง่ายที่สุด
㉖ JSON ใน Database ใช้ได้ไหม
ใช้ได้ในบาง Use Case เช่น Metadata ที่ยืดหยุ่น
ตัวอย่าง
{
"fuel": 80,
"color": 12
}
แต่ถ้าคุณต้อง Query Field นั้นบ่อย การยัดทุกอย่างลง JSON อาจทำให้ Database Design แย่ลง
ควรแยก Column ที่สำคัญต่อ Query/Indexing
㉗ Database Connector คืออะไร
FiveM Resource ไม่ได้พูดกับ MySQL เพียงด้วย Lua Syntax ธรรมดา
มักใช้ Connector/Database Resource เพื่อ
Lua/JS
↓
Database API
↓
MySQL/MariaDB
ตัวอย่างที่พบมากคือ
oxmysql
㉘ oxmysql คืออะไรแบบสั้น ๆ
oxmysql เป็น Resource/Library สำหรับเชื่อม FiveM Server Scripts กับ MySQL/MariaDB
รองรับ API เช่น
query
single
scalar
insert
update
prepare
transaction
หัวข้อ 452 จะลงรายละเอียด oxmysql โดยเฉพาะ
㉙ Database Query ควรอยู่ Client หรือ Server
Server
ไม่ควรให้ Client ต่อ Database โดยตรง
Architecture ที่ถูกต้องคือ
Client
↓
Request
↓
Server
↓
Validate
↓
Database
↓
Server
↓
Response
เพราะ Database Credentials และ Authority ต้องอยู่ Server-side
㉚ อย่าส่ง Database Password ไป Client
ห้ามใส่
database password
username
host
connection string
ไว้ใน client.lua หรือ shared.lua
ควรเก็บ Connection Configuration ฝั่ง Server
เช่น Server Convar ตาม Database Connector ที่ใช้
㉛ ตัวอย่าง oxmysql Connection String
เอกสาร oxmysql ปัจจุบันใช้ mysql_connection_string
ตัวอย่าง
set mysql_connection_string "mysql://user:password@localhost:3306/fivem"
หรือ Format อีกแบบตามที่ Library รองรับ
Connection String ต้องถูกตั้งก่อน Resource ที่ต้องใช้ Database
㉜ oxmysql ควร Start ตอนไหน
เอกสาร oxmysql แนะนำให้ Start ก่อน Resources ที่ใช้ Database
ตัวอย่าง
set mysql_connection_string "mysql://user:password@localhost:3306/fivem"
ensure oxmysql
ensure my_core
ensure my_inventory
ensure my_garage
เพื่อให้ Database Layer พร้อมก่อน Consumers
㉝ Resource ควรประกาศ Dependency ไหม
ถ้า Resource ต้องใช้ oxmysql จริง
สามารถประกาศ
dependency 'oxmysql'
ใน fxmanifest.lua
และจัด Startup Order ให้ชัด
ensure oxmysql
ensure my_resource
㉞ วิธีใช้ oxmysql ใน Lua
เอกสารปัจจุบันรองรับการ Import Library ผ่าน Manifest เช่น
server_script '@oxmysql/lib/MySQL.lua'
แล้วใน Server Lua สามารถใช้ APIs เช่น
local rows =
MySQL.query.await(
'SELECT * FROM users'
)
รายละเอียดเต็มจะอยู่ในหัวข้อ 452–455
㉟ SELECT คืออะไร
ใช้ดึงข้อมูล
SELECT *
FROM users
WHERE identifier = ?;
ตัวอย่าง Lua
local users =
MySQL.query.await(
'SELECT * FROM users WHERE identifier = ?',
{
identifier
}
)
ผลลัพธ์อาจเป็น Array ของ Rows ตาม API
㊱ INSERT คืออะไร
ใช้สร้าง Row ใหม่
INSERT INTO users (
identifier,
name
)
VALUES (?, ?);
เช่นสร้าง Character หรือ Vehicle Record ใหม่
㊲ UPDATE คืออะไร
ใช้แก้ข้อมูลเดิม
UPDATE users
SET cash = ?
WHERE id = ?;
เหมาะกับ Save Data ที่มี Record อยู่แล้ว
㊳ DELETE คืออะไร
ใช้ลบข้อมูล
DELETE FROM vehicles
WHERE id = ?;
ควรระวังมากเพราะเป็น Destructive Operation
Production System ควร Validate Owner/Permission/Business State ก่อน Delete
㊴ Parameter Placeholder คืออะไร
อย่าต่อ String Input เข้า SQL ตรง ๆ
ไม่ควร
local sql =
"SELECT * FROM users WHERE name = '"
.. name
.. "'"
ควรใช้ Placeholder
MySQL.query.await(
'SELECT * FROM users WHERE name = ?',
{
name
}
)
ช่วยลด SQL Injection Risk และทำให้ Query ชัดขึ้น
㊵ SQL Injection คืออะไร
คือการที่ Input ถูกนำไปประกอบ SQL แบบไม่ปลอดภัยจนผู้โจมตีเปลี่ยนความหมายของ Query ได้
เช่น
Client Input
↓
String Concatenation
↓
SQL
เป็น Pattern ที่ควรหลีกเลี่ยง
oxmysql Documentation ระบุว่า Placeholders ช่วยป้องกัน SQL Injection วิธีทั่วไป
㊶ Client Input ต้อง Validate ก่อน Query ไหม
ต้อง
Parameterized Query ไม่ได้แปลว่า Input ถูกต้องทาง Business Logic
สมมติ Client ส่ง
vehicle_id = 521
ถึง Query ปลอด SQL Injection แต่ Server ยังต้องตรวจว่า
รถ 521 เป็นของ Player คนนี้ไหม?
Player มีสิทธิ์ขายไหม?
รถถูกล็อกอยู่ไหม?
Security มีทั้ง Database Layer และ Gameplay Validation
㊷ Query กับ Validation เป็นคนละเรื่อง
ตัวอย่าง
local vehicle =
MySQL.single.await(
'SELECT * FROM vehicles WHERE id = ?',
{
vehicleId
}
)
หลังได้ Row ยังต้อง
if not vehicle then
return
end
if vehicle.character_id
~= characterId then
return
end
อย่าคิดว่าหา Row เจอ = Client มีสิทธิ์
㊸ Database Transaction คืออะไร
Transaction ใช้รวมหลาย Query ให้สำเร็จพร้อมกัน
Concept
BEGIN
↓
Query A
↓
Query B
↓
Query C
↓
ทุกตัวสำเร็จ
→ COMMIT
มีตัวใดล้มเหลว
→ ROLLBACK
oxmysql รองรับ MySQL.transaction
㊹ Transaction เหมาะกับอะไร
เช่นโอนเงิน
Player A
- 1000
Player B
+ 1000
ถ้า Query แรกสำเร็จแต่ Query ที่สองล้มเหลว ระบบจะผิด
Transaction ช่วยให้
ทั้งคู่สำเร็จ
หรือ
ไม่สำเร็จทั้งคู่
เหมาะกับ Operation ที่ต้อง Atomic
㊺ ตัวอย่าง Transaction
Concept
local success =
MySQL.transaction.await({
{
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
{ amount, senderId }
},
{
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
{ amount, receiverId }
}
})
ถ้ามี Query ใด Fail Transaction จะไม่ Commit ทั้งชุดตาม API Behavior
㊻ Prepared Query คืออะไร
Prepared Query เหมาะกับ Query ที่ถูกเรียกบ่อยและใช้ Parameter
oxmysql มี
MySQL.prepare
เอกสารระบุว่าสามารถใช้กับ Query ที่เรียกบ่อยเพื่อช่วยด้านประสิทธิภาพ
แต่ไม่จำเป็นต้องเปลี่ยน Query ทุกตัวเป็น Prepared Statement โดยไม่วัดผล
㊼ Index คืออะไร
Index ช่วยให้ Database หา Rows เร็วขึ้นสำหรับ Query Patterns ที่เหมาะสม
ตัวอย่างถ้าค้น
SELECT *
FROM vehicles
WHERE character_id = ?;
บ่อยมาก
Column
character_id
อาจควรมี Index
แต่ Index มากเกินไปก็เพิ่ม Cost ตอน INSERT/UPDATE และใช้พื้นที่เพิ่ม
㊽ Primary Key คือ Index ไหม
Primary Key จะถูกใช้เพื่อ Unique Identification และ Database จะมี Index ที่เกี่ยวข้อง
Query เช่น
SELECT *
FROM vehicles
WHERE id = ?;
จึงเหมาะกับ Primary Key Lookup
Database Design ที่ดีเริ่มตั้งแต่ Key Structure
㊾ Slow Query คืออะไร
คือ Query ที่ใช้เวลานานเกิน Threshold ที่กำหนด
oxmysql มี Slow Query Warning และ Debug UI เพื่อดู
Query
Execution Time
Resource
Slow Queries
แต่ Slow Query ไม่ได้หมายความว่า Database เสียเสมอไป เพราะ Server Hitch หรือ Load อื่นก็มีผลต่อเวลาที่รายงานได้
㊿ สาเหตุ Slow Query ที่พบบ่อย
เช่น
ไม่มี Index
SELECT ข้อมูลมากเกินไป
Query ซับซ้อน
Table ใหญ่
Disk ช้า
Database อยู่ไกล
Lock/Transaction
Query ซ้ำจำนวนมาก
ต้องดู Execution Pattern ไม่ใช่เพิ่ม Hardware อย่างเดียว
51 SELECT * ดีไหม
ใช้ได้ใน Query ง่ายหรือช่วง Development
แต่ Production Query ที่ต้องการเพียง
id
cash
ไม่จำเป็นต้อง
SELECT *
ทุก Column
สามารถใช้
SELECT id, cash
FROM users
WHERE identifier = ?;
ลดข้อมูลที่ส่งกลับและทำ Intent ชัดขึ้น
52 Database Query ใน Loop ต้องระวัง
ตัวอย่างไม่ดี
for _, player in pairs(players) do
MySQL.query.await(
'SELECT * FROM users WHERE id = ?',
{
player.id
}
)
end
ถ้ามี 200 Players จะเกิดหลาย Queries ต่อเนื่อง
บางกรณีควร Batch Query หรือโหลดข้อมูลครั้งเดียวแล้ว Cache
53 N+1 Query Problem คืออะไร
สมมติ
1 Query
โหลด Players
แล้ว
100 Queries
โหลด Vehicle ของแต่ละ Player
นี่เป็น N+1 Pattern
อาจแก้ด้วย
JOIN
IN (...)
Batch Query
Cache
ตาม Design
หัวข้อ Slow Query จะลงรายละเอียดต่อในบทความ 455
54 Database Cache ใช้อย่างไร
ตัวอย่าง Player Login
Database
↓
Load Character
↓
CharacterCache[source]
↓
Gameplay
↓
Save Changes
ทำให้ Gameplay ไม่ต้อง Query Database ทุกครั้งที่อ่าน Cash
แต่ต้องออกแบบ Save Strategy เพื่อไม่ให้ข้อมูลสูญหาย
55 Autosave ดีไหม
หลาย Framework มี Autosave Pattern
เช่น
ทุก X นาที
↓
Save Player Runtime State
รวมกับ
Player Logout
Resource/Server Lifecycle
Important Transactions
แต่ไม่ควร Save ทั้ง Database ทุก Tick
ต้องหา Balance ระหว่าง Durability กับ Query Load
56 Server Crash แล้วข้อมูลหายได้ไหม
ข้อมูลที่ยังอยู่เฉพาะ Runtime Memory และยังไม่ได้ Save มีโอกาสหายเมื่อ Server Crash
นี่เป็นเหตุผลที่ต้องคิด
Save Frequency
Transactions
Critical Immediate Saves
Graceful Shutdown
แต่ Save ทุกการเปลี่ยนเล็ก ๆ ก็อาจเพิ่ม Database Load
Architecture ต้องสมดุล
57 Backup Database สำคัญไหม
สำคัญมาก
Database เก็บข้อมูลที่ผู้เล่นสะสมเป็นเดือนหรือปี
ควรมี
Automated Backups
Retention
Off-machine Copy
Restore Testing
Backup ที่ไม่เคยทดสอบ Restore ยังไม่ควรถูกมองว่าปลอดภัยเต็มที่
58 Database Security ต้องระวังอะไร
อย่างน้อย
ไม่เปิด Database Port สู่ Internet โดยไม่จำเป็น
ใช้ Password แข็งแรง
จำกัด Database User Permissions
ไม่เก็บ Credentials ฝั่ง Client
ใช้ Parameterized Queries
Validate Client Input
Backup
Update Database Server
ถ้า FXServer กับ Database อยู่เครื่องเดียวกัน อาจจำกัด Database ให้ฟังเฉพาะ Local Interface ตาม Architecture
59 Checklist FiveM Database
ตรวจ
① ข้อมูลไหนต้อง Persistent?
② ข้อมูลไหนควรอยู่ Runtime?
③ Database ใช้ MySQL หรือ MariaDB?
④ Connector คืออะไร?
⑤ oxmysql Start ก่อน Consumer หรือไม่?
⑥ mysql_connection_string ถูกหรือไม่?
⑦ Credentials อยู่ Server-side หรือไม่?
⑧ Resource ประกาศ Dependency หรือไม่?
⑨ Player source ถูกใช้เป็น Permanent ID หรือไม่?
⑩ Network ID ถูกเก็บเป็น DB ID หรือไม่?
⑪ Entity Handle ถูกเก็บ Database หรือไม่?
⑫ Primary Keys ออกแบบหรือยัง?
⑬ Foreign/relationship IDs ชัดหรือไม่?
⑭ Columns มี Data Types เหมาะหรือไม่?
⑮ Query ใช้ Placeholders หรือไม่?
⑯ Client Input Validate หรือไม่?
⑰ SQL Injection Risk มีหรือไม่?
⑱ Transaction ใช้กับ Atomic Operation หรือไม่?
⑲ Query ใน Loop มากเกินไปหรือไม่?
⑳ Index ตรงกับ Query Patterns หรือไม่?
㉑ Slow Queries ถูกตรวจหรือไม่?
㉒ Autosave มากเกินไปหรือไม่?
㉓ Crash แล้ว Data Loss Window เท่าไร?
㉔ Backup มีหรือไม่?
㉕ Restore เคยทดสอบหรือไม่?
⑥⓪ ตัวอย่าง FiveM Database Flow แบบครบ
สมมติระบบ Garage
Player เข้า Server
FXServer
↓
หา Character ID
↓
SELECT vehicles
↓
Database
↓
คืน Vehicle Rows
↓
Server Cache
Player เปิด Garage
Client
↓
Trigger Request
↓
Server
↓
อ่าน Cache/Database
↓
ตรวจ Ownership
↓
ส่ง Vehicle List
↓
Client UI
Playerเบิกรถ
Client
↓
ขอ Vehicle ID 521
↓
Server
↓
ตรวจ Character
↓
ตรวจ Vehicle Owner
↓
ตรวจ stored state
↓
Server Create Vehicle
↓
Update Database stored = 0
↓
ส่ง Network ID
↓
Client
เมื่อเก็บรถ
Client
↓
Request Store
↓
Server
↓
ตรวจ Entity
↓
ตรวจ Vehicle DB ID
↓
ตรวจ Ownership
↓
Save Vehicle Properties
↓
UPDATE Database
↓
Delete Runtime Entity
นี่คือการทำงานร่วมกันของ
Database ID
+
Server Validation
+
Runtime Entity
+
Network ID
โดยแต่ละ ID มีหน้าที่ต่างกัน
ตัวอย่าง Query โหลด Player แบบปลอดภัย
local function loadPlayer(
identifier
)
if type(identifier)
~= 'string' then
return nil
end
local player =
MySQL.single.await(
[[
SELECT
id,
identifier,
cash,
bank
FROM users
WHERE identifier = ?
LIMIT 1
]],
{
identifier
}
)
return player
end
ข้อดีคือ
ไม่ต่อ SQL String
เลือกเฉพาะ Columns ที่ใช้
ใช้ Placeholder
LIMIT 1
ตัวอย่างสร้าง Character
local characterId =
MySQL.insert.await(
[[
INSERT INTO characters (
identifier,
firstname,
lastname
)
VALUES (?, ?, ?)
]],
{
identifier,
firstName,
lastName
}
)
ก่อน Query ยังต้อง Validate
identifier
firstname
lastname
ทาง Server-side
ตัวอย่าง Update Money
local affectedRows =
MySQL.update.await(
[[
UPDATE characters
SET cash = ?
WHERE id = ?
]],
{
cash,
characterId
}
)
แต่ใน Economy System จริงมักไม่ควรให้ Clientส่ง cash มาเป็นค่าที่ Server เชื่อโดยตรง
Server ต้องคำนวณ Current Balance เอง
ตัวอย่าง Transaction โอนเงิน
local success =
MySQL.transaction.await({
{
[[
UPDATE accounts
SET balance =
balance - ?
WHERE id = ?
AND balance >= ?
]],
{
amount,
senderId,
amount
}
},
{
[[
UPDATE accounts
SET balance =
balance + ?
WHERE id = ?
]],
{
amount,
receiverId
}
}
})
Production Economy ยังต้องตรวจ Affected Rows, Business Rules และ Concurrency ตาม Architecture
Database ไม่ควรถูกมองเป็น Anti-Cheat
มี Database ไม่ได้แปลว่า Server ปลอดภัย
ถ้ามี Event
RegisterNetEvent(
'player:setMoney',
function(amount)
MySQL.update.await(
'UPDATE users SET cash = ? WHERE id = ?',
{
amount,
getPlayerId(source)
}
)
end
)
แล้วเชื่อ amount จาก Client
Database ก็เพียงบันทึกค่าที่โกงลงไปอย่างถาวร
หลักคือ
Client Request
↓
Server Validate
↓
Server Calculate
↓
Database Persist
ไม่ใช่
Client Dictates
↓
Database Save
Database กับ State Bag ต่างกันอย่างไร
State Bag
Runtime replicated state
Database
Persistent storage
ตัวอย่าง
Vehicle fuel ตอนนี้
→ State Bag อาจเหมาะกับ Runtime Sync
Vehicle เป็นของ Character ไหน
→ Database
แต่ Fuel ที่ต้องอยู่ข้าม Restart อาจต้อง Save Database ด้วย
สองระบบสามารถใช้ร่วมกันได้
Database กับ GlobalState ต่างกันอย่างไร
GlobalState เหมาะกับ Runtime State ที่ต้อง Replicate
เช่น
Server Event Active = true
แต่ไม่ใช่ Persistent Database
หาก FXServer Restart GlobalState ไม่ได้แทนข้อมูลถาวร
Database กับ KVP ต่างกันอย่างไร
KVP เหมาะกับ Key/Value Data บางประเภท
Database เหมาะกับข้อมูล Relational/Queryable ขนาดใหญ่และหลายผู้ใช้
ไม่ควรใช้ Database สำหรับ Setting เล็กทุกตัวเสมอไป
และไม่ควรใช้ KVP แทน Relational Database สำหรับระบบ Character/Vehicle ใหญ่ ๆ โดยไม่มีเหตุผล
คำถามที่พบบ่อยเกี่ยวกับ FiveM Database
FiveM Database คืออะไร
คือระบบ Persistent Storage ที่ใช้เก็บข้อมูลเซิร์ฟเวอร์ เช่น Player, Character, Money, Inventory, Vehicle และ Property
FiveM จำเป็นต้องมี Database ไหม
ไม่ทุก Resource แต่ Roleplay/Framework Server ที่ต้องเก็บข้อมูลถาวรแทบทั้งหมดต้องใช้ Persistent Storage
FiveM ใช้ MySQL ได้ไหม
ได้ และมี Database Connector Resources สำหรับเชื่อม Server Scripts กับ MySQL/MariaDB
MariaDB ใช้กับ FiveM ได้ไหม
ได้ และ oxmysql Documentation ปัจจุบันแนะนำ MariaDB สำหรับ Compatibility กับ Resources FiveM จำนวนมาก
oxmysql คือ Database ไหม
ไม่ oxmysql เป็น Connector/Library ระหว่าง FiveM Resource กับ MySQL/MariaDB
Database อยู่ใน FXServer ไหม
โดยทั่วไป Database Server เป็น Service แยก แม้อาจรันอยู่บนเครื่องเดียวกับ FXServer
Client ต่อ Database ได้ไหม
ไม่ควร Architecture ที่ถูกต้องให้ Client Request ไป Server แล้ว Server เป็นผู้ Query Database
Database Password ใส่ใน client.lua ได้ไหม
ไม่
source เก็บ Database ได้ไหม
ไม่ควรใช้เป็น Permanent Player Identity เพราะ Server ID เปลี่ยนระหว่าง Sessions
Entity Handle เก็บ Database ได้ไหม
ไม่ควร
Network ID เก็บ Database ได้ไหม
ไม่ควรใช้เป็น Permanent Identity
Vehicle ควรมี DB ID ไหม
สำหรับระบบ Persistent Vehicle การมี Stable ID/Primary Key เป็น Architecture ที่ดีมาก
SQL คืออะไร
ภาษาสำหรับ Query และจัดการ Relational Database
SELECT คืออะไร
อ่านข้อมูล
INSERT คืออะไร
เพิ่ม Row
UPDATE คืออะไร
แก้ Row
DELETE คืออะไร
ลบ Row
Placeholder สำคัญไหม
สำคัญ เพราะช่วย Execute Parameters อย่างปลอดภัยและลด SQL Injection Risk
ใช้ String ต่อ SQL ได้ไหม
ไม่ควรกับ Input โดยเฉพาะข้อมูลจาก Client
Transaction คืออะไร
กลุ่ม Queries ที่ Commit ทั้งหมดเมื่อสำเร็จครบ และไม่ Commit หากชุดล้มเหลวตาม Transaction Behavior
Transfer Money ควรใช้ Transaction ไหม
เหมาะมาก เพราะการหักและเพิ่มเงินควรเป็น Atomic Operation
Query Database ทุก Frame ได้ไหม
ไม่ควร
Database ช้าแก้อย่างไร
ต้องตรวจ Slow Query, Index, Query Design,จำนวน Queries, Hardware และ Network/Server Hitches
Backup Database จำเป็นไหม
จำเป็นสำหรับ Production Server
Resource Restart แล้วข้อมูล Database หายไหม
ข้อมูลที่ Commit แล้วใน Database ไม่หายเพียงเพราะ Resource Restart
Lua Table หายไหม
Runtime Table จะถูกสร้างใหม่เมื่อ Resource Restart
Database ทำให้ Resource ปลอด Cheat ไหม
ไม่ Server Validation ยังจำเป็น
สรุป FiveM Database คืออะไร ใช้ทำอะไรในเซิร์ฟเวอร์
FiveM Database คือระบบจัดเก็บข้อมูลถาวรที่ช่วยให้ข้อมูลของผู้เล่นและเซิร์ฟเวอร์ยังคงอยู่หลัง Disconnect, Resource Restart หรือ FXServer Restart
ข้อมูลอย่าง
Character
Money
Inventory
Vehicles
Properties
Businesses
ไม่ควรเก็บไว้เพียง
local Data = {}
ถ้าต้อง Persistence
Architecture หลักคือ
FiveM Client
↓
Server Resource
↓
Validation
↓
Database Connector
↓
MySQL / MariaDB
โดย Database Connector เช่น oxmysql ทำหน้าที่เชื่อม Server Script กับ Database และให้ APIs อย่าง
query
single
scalar
insert
update
prepare
transaction
Database ที่ดีต้องแยก Identity ให้ถูกด้วย
source
→ Session Player ID
Entity Handle
→ Runtime Entity Reference
Network ID
→ Runtime Network Reference
Database ID
→ Persistent Record Identity
ห้ามนำ ID เหล่านี้มาใช้แทนกันแบบสุ่ม
อีกเรื่องสำคัญคือ Database ไม่ควรถูก Query ทุกครั้งที่ Gameplay ต้องอ่านข้อมูลหนึ่งค่า
แนวทางที่พบบ่อยคือ
Database
↓
Load
↓
Runtime Cache
↓
Gameplay
↓
Save
↓
Database
และใช้ Transaction กับ Operation ที่ต้องสำเร็จครบชุด เช่น
โอนเงิน
ซื้อสินค้า
ซื้อรถ
เปลี่ยน Ownership
เพื่อช่วยป้องกันข้อมูลค้างอยู่ในสถานะครึ่งหนึ่ง
สำหรับ Developer ที่เรียน FiveM กับ comsiam วิธีคิดที่สำคัญคือ Database ใช้เก็บสิ่งที่ต้องอยู่ข้าม Runtime ส่วน Lua Tables, State Bags และ Entities ใช้จัดการ State ระหว่างที่ Server กำลังทำงาน
หลักจาก comsiam อีกข้อคือ อย่าให้ Client เป็นคนกำหนดข้อมูลสำคัญแล้วให้ Database บันทึกตามนั้น Client ควรส่ง Request ส่วน Server ตรวจ Permission, State และ Business Rules ก่อน แล้วจึง Query หรือ Update Database
หัวข้อถัดไปคือ oxmysql FiveM คืออะไร ทำไม Script จำนวนมากถึงใช้ ซึ่งจะลงรายละเอียดว่า oxmysql เป็น Connector อย่างไร, ทำไมใช้กับ MySQL/MariaDB, API แต่ละแบบต่างกันอย่างไร และควรตั้ง mysql_connection_string กับ fxmanifest.lua แบบไหน
Comments
Post a Comment