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

ididentifiernamecash
1license:abcSomchai5000
2license:defMana12000

แต่ละแถวคือ 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

Popular posts from this blog

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

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

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