FiveM MySQL.prepare คืออะไร? วิธีใช้ Prepared Query ใน oxmysql ให้ Query ที่เรียกบ่อยทำงานมีประสิทธิภาพขึ้น

 FiveM MySQL.prepare คือฟังก์ชันของ oxmysql สำหรับ Execute Query ที่ถูกเรียกซ้ำบ่อย โดย Official oxmysql Documentation ระบุว่า Prepare เหมาะกับ Frequently-called Queries และสามารถรับ Parameter หลายชุดสำหรับ SQL Query เดียวกันได้.

ต่างจาก MySQL.query, MySQL.single, MySQL.scalar หรือ MySQL.update ที่เน้นรูปแบบผลลัพธ์ของ Query เป็นหลัก MySQL.prepare มีจุดเด่นคือ Query ถูกจัดการในลักษณะ Prepared Statement ฝั่ง MySQL Server และเหมาะเมื่อ SQL Structure เดิมถูกเรียกซ้ำพร้อม Parameters ที่เปลี่ยนไป.

อย่างไรก็ตาม การเปลี่ยนทุก Query เป็น MySQL.prepare ไม่ได้ทำให้ FiveM Server เร็วขึ้นโดยอัตโนมัติ เพราะ Query Performance ยังขึ้นกับ Hardware, Database Settings, Database Version, Current Workload, SQL Structure และ Index ด้วย โดย oxmysql แนะนำให้ดูความเร็วจริงผ่าน Debug UI หรือ mysql_debug.

① MySQL.prepare คืออะไร

รูปแบบพื้นฐาน:

local result = MySQL.prepare.await(
    'SELECT `firstname`, `lastname` FROM `users` WHERE `identifier` = ?',
    {
        identifier
    }
)

Official oxmysql Documentation รองรับ Promise/Await Style แบบนี้โดยตรง.

จำง่ายๆ:

SQL Structure เดิม
+
เรียกซ้ำบ่อย
+
Parameters เปลี่ยน
=
MySQL.prepare

② Prepared Statement คืออะไร

แนวคิดพื้นฐานคือแยก:

SQL Structure

ออกจาก:

Parameter Values

ตัวอย่าง SQL:

SELECT `firstname`, `lastname`
FROM `users`
WHERE `identifier` = ?

ส่วน:

?

คือ Placeholder สำหรับค่าที่จะถูกส่งเข้ามาภายหลัง

oxmysql ระบุว่า MySQL.prepare ใช้ Prepared Statements ที่จัดการโดย MySQL Server และแตกต่างจาก Placeholder ธรรมดาที่ใช้ใน Query APIs.

③ MySQL.prepare เหมาะเมื่อไหร่

เหมาะกับ Query เช่น:

โหลดข้อมูล Player
ตรวจ Character
อ่าน Player Settings
อ่าน Persistent Profile
ตรวจ Vehicle Record
อัปเดตข้อมูลเดิมซ้ำๆ

โดยเฉพาะเมื่อ Query Structure เหมือนเดิมแต่ Parameter เปลี่ยน

ตัวอย่าง:

SELECT `id`
FROM `characters`
WHERE `identifier` = ?
LIMIT 1

ถูกเรียกกับผู้เล่นหลายคน

④ ตัวอย่าง Query ที่ถูกเรียกบ่อย

local character =
    MySQL.prepare.await(
        [[
            SELECT
                `id`,
                `firstname`,
                `lastname`
            FROM `characters`
            WHERE `identifier` = ?
            LIMIT 1
        ]],
        {
            identifier
        }
    )

oxmysql ระบุว่า Prepare ถูกออกแบบให้ใช้กับ Query ที่ถูกเรียกบ่อยในลักษณะนี้.

⑤ MySQL.prepare กับ MySQL.query ต่างกันอย่างไร

MySQL.query

ใช้ Query ทั่วไป และเมื่อเป็น SELECT จะคืน Rows และ Columns ที่ตรงเงื่อนไขทั้งหมด.

MySQL.prepare

ออกแบบสำหรับ Query ที่ถูกเรียกบ่อยและใช้ Prepared Statement.

จำ:

query
= General Query

prepare
= Frequently-called Prepared Query

⑥ MySQL.prepare กับ MySQL.single ต่างกันอย่างไร

MySQL.single ถูกออกแบบให้คืน:

หนึ่ง Row

โดยตรง.

ตัวอย่าง:

local row =
    MySQL.single.await(
        'SELECT `firstname`, `lastname` FROM `users` WHERE `identifier` = ? LIMIT 1',
        {
            identifier
        }
    )

ส่วน prepare เน้น Prepared Execution มากกว่าการบังคับ Result Shape

⑦ MySQL.prepare คืนค่าอย่างไรเมื่อ SELECT

oxmysql ระบุว่า prepare จะคืนผลตามจำนวน Rows และ Columns ที่ Query เลือก ซึ่งอาจเป็น:

Column
Row
หรือ
Array of Rows

ตาม Result ของ SELECT.

จุดนี้ต่างจาก rawExecute ซึ่ง SELECT จะคืน Array of Rows เสมอ.

⑧ MySQL.prepare กับ MySQL.scalar ต่างกันอย่างไร

MySQL.scalar คืนค่า:

Column แรก
ของ Row เดียว

Official Example เช่น:

local firstName =
    MySQL.scalar.await(
        'SELECT `firstname` FROM `users` WHERE `identifier` = ? LIMIT 1',
        {
            identifier
        }
    )

ถ้าต้องการค่าหนึ่งค่าและ Query ไม่ได้ถูกเรียกหนักเป็นพิเศษ scalar อ่าน Intent ง่ายกว่า

⑨ MySQL.prepare กับ MySQL.update ต่างกันอย่างไร

MySQL.update คืน:

จำนวน Rows ที่ได้รับผล

จาก Update Query.

ตัวอย่าง:

local affected =
    MySQL.update.await(
        'UPDATE `users` SET `firstname` = ? WHERE `identifier` = ?',
        {
            firstName,
            identifier
        }
    )

ถ้า Update Query เดิมถูกเรียกถี่มากจึงค่อยพิจารณา prepare

⑩ MySQL.prepare กับ Transaction ต่างกันอย่างไร

Prepare

Query เดิมถูกเรียกบ่อย

Transaction

หลาย Queries
ต้องสำเร็จทั้งหมดก่อน Commit

oxmysql ระบุว่า Transaction จะ Commit เมื่อทุก Query สำเร็จ และถ้าตัวใดล้มเหลวจะไม่ Commit ชุดนั้น.

ดังนั้นสองฟังก์ชันนี้แก้คนละปัญหา

⑪ Prepare ทำให้ Query เร็วขึ้นจริงไหม

Official Documentation ระบุว่า Prepare สามารถใช้ Execute Frequently-called Queries ได้เร็วขึ้น.

แต่ไม่ควรแปลว่า:

prepare
=
เร็วกว่า query ทุกกรณี

เพราะ oxmysql ระบุว่าความเร็วจริงแตกต่างตาม Hardware, Database Settings, Database Version และ Current Workload.

ดังนั้นต้อง Benchmark

⑫ อย่าเปลี่ยนทุก Query เป็น Prepare

ตัวอย่าง Query:

เรียกตอน Resource Start ครั้งเดียว

ไม่จำเป็นต้อง Rewrite เป็น Prepare เพียงเพื่อ Optimization

ให้เน้น Query ที่:

เรียกซ้ำบ่อย
+
Structure เดิม
+
Parameters เปลี่ยน

ก่อน

⑬ Value Placeholder ใช้อะไร

กับ MySQL.prepare ใช้:

?

สำหรับ Value Placeholder

oxmysql ระบุว่า Prepare รองรับ ? สำหรับ Value Parameters.

ตัวอย่าง:

SELECT `id`
FROM `users`
WHERE `identifier` = ?

⑭ Column Placeholder ใช้อะไร

Prepare รองรับ:

??

เป็น Column Placeholder ตาม Documentation ปัจจุบัน.

แต่ Dynamic Column Queries ควรถูกออกแบบอย่างระมัดระวัง เพราะหลายระบบสามารถหลีกเลี่ยง Dynamic SQL ได้ด้วยการกำหนด Columns ฝั่ง Server

⑮ Named Placeholder ใช้กับ Prepare ได้ไหม

ไม่ได้ตาม Documentation ปัจจุบัน

oxmysql ระบุว่า MySQL.prepare รองรับ:

?
??

แต่ Named Placeholders จะ Error.

ดังนั้นหลีกเลี่ยง:

WHERE identifier = @identifier

ใน MySQL.prepare

⑯ Named Placeholder ใน Query ทั่วไปเป็นอย่างไร

oxmysql Placeholder Documentation ระบุ Named Placeholders เช่น:

@identifier

ว่าเป็นรูปแบบที่ Deprecated และแนะนำ Parameter Placeholder แบบ ? สำหรับ Code ใหม่.

ดังนั้นมาตรฐานที่ควรใช้ต่อคือ:

WHERE `identifier` = ?

⑰ Placeholder ช่วยเรื่องอะไร

oxmysql ระบุว่า Placeholders ช่วย Execute Parameters อย่างปลอดภัยและลดวิธี SQL Injection ทั่วไปได้.

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

MySQL.prepare.await(
    'SELECT `id` FROM `users` WHERE `identifier` = ?',
    {
        identifier
    }
)

แทนการต่อ String ด้วยข้อมูลจาก Player

⑱ อย่าต่อ Player Input เข้า SQL

หลีกเลี่ยง:

local sql =
    "SELECT id FROM users WHERE identifier = '" ..
    identifier ..
    "'"

ใช้:

local id =
    MySQL.prepare.await(
        'SELECT `id` FROM `users` WHERE `identifier` = ?',
        {
            identifier
        }
    )

Placeholder ทำให้ SQL Structure กับ Parameter ถูกแยกออกจากกัน.

⑲ MySQL.prepare.await คืออะไร

.await คือ Promise/Await Style ของ Lua API

ตัวอย่าง:

local result =
    MySQL.prepare.await(
        query,
        params
    )

-- ใช้ result ต่อ

Official Prepare API รองรับรูปแบบนี้.

⑳ Callback ใช้ได้ไหม

Prepare API รองรับ Callback Style เช่นกัน

แนวคิด:

MySQL.prepare(
    query,
    params,
    function(result)

        -- ใช้ผลลัพธ์

    end
)

ดังนั้นเลือก:

await
หรือ
callback

ตาม Code Style ของ Resource

㉑ ต้อง import oxmysql ก่อนหรือไม่

สำหรับ Lua Resource oxmysql Documentation แนะนำเพิ่ม:

server_script '@oxmysql/lib/MySQL.lua'

ใน fxmanifest.lua ก่อน Scripts อื่นที่ต้องใช้ MySQL Library.

ตัวอย่าง:

fx_version 'cerulean'
game 'gta5'

server_script '@oxmysql/lib/MySQL.lua'
server_script 'server.lua'

㉒ ทำไมต้องใส่ MySQL.lua ก่อน server.lua

เพราะ server.lua จะเรียก:

MySQL.prepare.await(...)

ดังนั้น Library ต้องถูกโหลดก่อน Script ที่เรียก API

oxmysql ระบุว่าการ Import Library ยังช่วยเรื่อง Type-checking และลด Overhead บางส่วนเมื่อเทียบกับ Raw Export Calls.

㉓ ตัวอย่าง Prepare สำหรับ Character Load

local function loadCharacter(identifier)

    return MySQL.prepare.await(
        [[
            SELECT
                `id`,
                `firstname`,
                `lastname`
            FROM `characters`
            WHERE `identifier` = ?
            LIMIT 1
        ]],
        {
            identifier
        }
    )

end

เหมาะเมื่อ Character Lookup รูปแบบเดิมเกิดซ้ำบ่อย

㉔ ตัวอย่าง Prepare สำหรับ Player Settings

local function loadSettings(identifier)

    return MySQL.prepare.await(
        [[
            SELECT
                `theme`,
                `language`
            FROM `player_settings`
            WHERE `identifier` = ?
            LIMIT 1
        ]],
        {
            identifier
        }
    )

end

Resource สามารถเรียก Function นี้ด้วย Identifier ที่ต่างกันโดยใช้ SQL Structure เดิม

㉕ ตัวอย่าง Prepare สำหรับ Vehicle Lookup

local vehicles =
    MySQL.prepare.await(
        [[
            SELECT
                `id`,
                `model`,
                `stored`
            FROM `vehicles`
            WHERE `owner` = ?
              AND `stored` = ?
        ]],
        {
            identifier,
            1
        }
    )

Prepared Query ไม่ได้แทน Index

ถ้า vehicles Table ใหญ่ ยังต้องตรวจ Database Index และ Query Plan แยกต่างหาก

㉖ Prepare แทน Index ได้ไหม

ไม่ได้

Prepare:

จัดการ Query ที่เรียกซ้ำ

Index:

ช่วย Database ค้น Rows

ถ้า Query:

WHERE `owner` = ?

ช้าเพราะ Table ใหญ่และไม่มี Index ที่เหมาะสม การใช้ Prepare อย่างเดียวไม่แก้ Table Access Pattern

㉗ Prepare + Index ใช้ร่วมกันได้ไหม

ได้ และมักเป็นสิ่งที่พบในระบบจริง

ตัวอย่าง Query:

SELECT
    `id`,
    `model`
FROM `vehicles`
WHERE `owner` = ?
  AND `stored` = ?

Resource อาจใช้ MySQL.prepare เพราะเรียกบ่อย

ขณะเดียวกัน Database อาจมี Index:

CREATE INDEX `idx_vehicles_owner_stored`
ON `vehicles` (`owner`, `stored`);

แต่ Index ต้องออกแบบจาก Query/Execution Plan จริง ไม่ควรเพิ่มเพราะเห็นตัวอย่างเท่านั้น

㉘ Prepare แทน Cache ได้ไหม

ไม่ได้

Prepare

ยัง Query Database

Cache

อาจไม่ต้อง Query Database

ถ้า Player Settings ไม่เปลี่ยนตลอดเวลา Architecture ที่ดีกว่าอาจเป็น:

Login
↓
Prepare Query Load
↓
เก็บ Runtime Cache
↓
ใช้ Cache ระหว่าง Session
↓
Save เมื่อเปลี่ยน

แทน Query Database ทุกครั้งที่ Code ต้องอ่านค่าเดิม

㉙ Prepare ไม่ได้ทำให้ Query ทุก Frame ถูกต้อง

ตัวอย่าง:

CreateThread(function()
    while true do

        MySQL.prepare.await(
            'SELECT `status` FROM `users` WHERE `identifier` = ?',
            {
                identifier
            }
        )

        Wait(0)

    end
end)

แม้ใช้ Prepare ก็ยัง Query Database ทุก Frame

นี่เป็น Architecture ที่ควรแก้ก่อน Micro-optimization

㉚ ถ้าข้อมูลเปลี่ยนน้อยควรทำอย่างไร

ใช้ Pattern:

Database
↓
Load
↓
Cache

State เปลี่ยน
↓
Update Cache
↓
Save Database

แทน:

ทุกครั้งที่ต้องอ่าน
↓
Database Query

Prepare เหมาะเมื่อ Query จำเป็นต้องเกิดจริง ไม่ใช่เครื่องมือแก้ Excessive Query Frequency

㉛ Prepare รองรับหลาย Parameter Sets หรือไม่

รองรับ

Official Documentation ระบุว่า Prepare สามารถรับ Parameter Sets หลายชุดสำหรับ Query เดียวกัน.

แนวคิด:

SQL เดียว

Parameters A
Parameters B
Parameters C

เหมาะกับ Batch-like Execution บาง Use Cases

㉜ อย่าส่ง Parameter Sets จำนวนมหาศาลโดยไม่ Benchmark

แม้ API รองรับหลาย Parameter Sets แต่ไม่ได้หมายความว่าควรส่ง:

100,000 Parameter Sets

ใน Operation เดียว

ต้องพิจารณา:

Database Workload
Memory
Query Complexity
Player Count
Response Size

และวัดจริง

㉝ Prepare SELECT Result Shape ต้องระวัง

oxmysql ระบุว่า Prepare SELECT อาจคืน:

หนึ่ง Column
หนึ่ง Row
หลาย Rows

ตามจำนวน Columns และ Rows.

ดังนั้น Developer ต้องรู้ว่า Query คาดหวังอะไร

อย่าเขียน:

for i = 1, #result do

โดยไม่แน่ใจว่า result เป็น Array จริง

㉞ ต้องการ Array เสมอใช้อะไร

rawExecute ระบุว่า SELECT จะคืน Array of Rows เสมอ ซึ่งต่างจาก prepare.

แต่ไม่ควรเปลี่ยนไปใช้ rawExecute เพียงเพราะไม่อยากตรวจ Result Type โดยไม่เข้าใจ API

เลือก Function ให้ตรงกับ Intent

㉟ MySQL.rawExecute คืออะไร

oxmysql ระบุว่า rawExecute สามารถใช้กับ Frequently-called Queries และรับ Parameter Sets หลายชุดเช่นเดียวกัน แต่ Result Behavior ของ SELECT ต่างจาก Prepare.

ดังนั้น:

prepare
และ
rawExecute

มีความใกล้เคียงบางส่วน แต่ไม่ได้มี Output Contract เหมือนกัน

㊱ Date Result ใน Prepare มีข้อควรระวังอะไร

Official Prepare Documentation ระบุว่า Date Values จะไม่คืน Datestring รูปแบบที่ผู้ใช้ FiveM มักคุ้นเคยเหมือน APIs บางประเภท.

ดังนั้น Resource ที่ใช้:

DATETIME
TIMESTAMP
DATE

ควรตรวจ Return Type จริงก่อนนำไป Format

㊲ TINYINT(1) คืน Boolean ไหม

Prepare Documentation ระบุว่า TINYINT 1 และ BIT ไม่ได้คืน Boolean โดยอัตโนมัติใน Result Behavior นี้.

ดังนั้นอย่าเขียน:

if row.enabled == true then

โดยไม่ตรวจค่าจริง

อาจต้อง Normalize:

local enabled =
    row.enabled == 1

ตาม Result ที่ Resource ได้จริง

㊳ Query Result Type ต้องทดสอบก่อน Production

โดยเฉพาะ Columns:

DATE
DATETIME
BIT
TINYINT(1)
JSON
DECIMAL

ควร Print/Test Result ใน Development Database ก่อน

อย่าพึ่งสมมติ Type จาก SQL Schema เพียงอย่างเดียว

㊴ Prepare เหมาะกับ UPDATE ที่เรียกบ่อยไหม

สามารถใช้ Prepared Query สำหรับ SQL Structure ที่เรียกบ่อยได้ตาม API แต่ถ้าต้องการ:

affectedRows

อย่างชัดเจน MySQL.update มี Return Contract ที่อ่านง่ายกว่า.

ดังนั้นต้อง Balance ระหว่าง:

Performance Need
กับ
API Clarity

㊵ Prepare เหมาะกับ INSERT ไหม

สามารถ Execute SQL ผ่าน Prepare ได้ แต่ถ้าต้องการ:

Insert ID

MySQL.insert ถูกออกแบบให้ Insert Record และคืน Insert ID โดยตรง.

อย่าเลือก Prepare แล้วทำให้ Resourceซับซ้อนขึ้นโดยไม่มีเหตุผล

㊶ Prepare เหมาะกับ COUNT Query ไหม

ถ้า Query เดิมถูกเรียกซ้ำมากสามารถพิจารณาได้

ตัวอย่าง:

local count =
    MySQL.prepare.await(
        [[
            SELECT COUNT(*)
            FROM `characters`
            WHERE `identifier` = ?
        ]],
        {
            identifier
        }
    )

แต่ถ้า Query ต้องการค่าเดียวและไม่ได้เรียกถี่มาก MySQL.scalar อาจสื่อ Intent ได้ดีกว่า เพราะ API นี้คืน Column แรกของ Row เดียวโดยตรง.

㊷ Prepare กับ Upsert ใช้ร่วมกันได้ไหม

ได้

oxmysql Documentation แนะนำ Upsert Pattern เช่น:

INSERT INTO ...
VALUES (...)
ON DUPLICATE KEY UPDATE ...

และใช้ MySQL.prepare ในตัวอย่าง Upsert ของ Documentation หลัก.

แนวคิด:

Record ไม่มี
→ INSERT

Record มีแล้ว
→ UPDATE

ช่วยลด Pattern แบบ Query ตรวจว่ามีก่อนแล้วค่อยเลือก Insert/Update ใน Use Case ที่เหมาะสม

㊸ ตัวอย่าง Prepare Upsert

MySQL.prepare.await(
    [[
        INSERT INTO `player_settings`
            (`identifier`, `theme`)
        VALUES (?, ?)
        ON DUPLICATE KEY UPDATE
            `theme` = VALUES(`theme`)
    ]],
    {
        identifier,
        theme
    }
)

Upsert ต้องมี Key/Constraint ที่สอดคล้องกับ Duplicate Detection ของ Schema

㊹ Upsert แทน Transaction ได้ไหม

ไม่

Upsert:

หนึ่ง Logical Insert/Update Operation

Transaction:

หลาย Queries
ต้อง Commit พร้อมกัน

oxmysql Transaction ยังคงเหมาะเมื่อต้อง Atomic หลาย Operations.

㊺ Prepare กับ Transaction ใช้ร่วมกันเมื่อไร

ต้องแยก Requirement

ถ้าต้อง:

Query เดิมเรียกบ่อย

→ Prepare

ถ้าต้อง:

หลาย Query ต้องสำเร็จทั้งหมด

→ Transaction

ไม่ควรเลือกจากคำว่า “เร็วกว่า” เพียงอย่างเดียว

㊻ Query ที่ใช้ Prepare ยังต้องใช้ LIMIT ไหม

ถ้าต้องการ Record เดียวและ Query Logic รองรับก็ควรระบุ Intent ใน SQL

เช่น:

SELECT
    `id`,
    `firstname`
FROM `characters`
WHERE `identifier` = ?
LIMIT 1

Prepare ไม่ได้เพิ่ม LIMIT ให้เอง

㊼ Prepare ลด SELECT * ให้อัตโนมัติไหม

ไม่

ถ้าเขียน:

SELECT *
FROM `characters`
WHERE `identifier` = ?

Prepare ยัง Execute SQL นี้ตามเดิม

ดังนั้นควรเลือก Columns เท่าที่ Resource ต้องใช้:

SELECT
    `id`,
    `firstname`,
    `lastname`
FROM `characters`
WHERE `identifier` = ?

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

ไม่ได้

Slow Query อาจเกิดจาก:

ไม่มี Index
Query Structure ไม่เหมาะ
Result ใหญ่
Table ใหญ่
Database CPU สูง
Remote DB Latency
Query Frequency

ดังนั้นถ้า Prepare แล้วยังช้า ต้องกลับไปตรวจ Query Plan และ Database Design

㊾ วิธีตรวจ Prepare เร็วขึ้นจริงไหม

ใช้:

Before
↓
วัด Query

เปลี่ยนเป็น Prepare
↓
ใช้ Scenario เดิม

After
↓
วัด Query

oxmysql ระบุว่าความเร็ว Query จริงควรดูผ่าน Debug UI หรือ Server Console เมื่อเปิด mysql_debug.

㊿ เปิด mysql_debug อย่างไร

เพิ่ม:

set mysql_debug true

oxmysql จะ Print Queries ใน Server Console.

ถ้า Server มี Resources เยอะสามารถกำหนดเฉพาะ Resource ที่ต้องการ Debug ได้

51. Debug UI ช่วยอะไร

oxmysql Debug UI แสดง:

Queries
Response Time
Resource Statistics
Total Query Count
Slow Queries

และสามารถ Filter ตาม Resource ได้.

จึงเหมาะสำหรับเปรียบเทียบ:

query
vs
prepare

ภายใต้ Scenario เดียวกัน

52. Slow Query Threshold เท่าไร

Debug UI Documentation ระบุว่า Queries ที่เกิน mysql_slow_query_warning จะถูกแสดงเป็นสีส้ม และ Default ปัจจุบันคือ:

150 ms

แต่ Threshold นี้ไม่ได้หมายความว่า Query 151 ms พังเสมอ

ต้องดู Workload และ Frequency ด้วย

53. Prepare Query 2 ms แต่เรียก 100,000 ครั้งยังมีปัญหาไหม

อาจมี

เพราะ:

2 ms
×
จำนวน Query มหาศาล

ยังสร้าง Workload รวมได้

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

Query Time
+
Query Count

Debug UI ของ oxmysql แสดงข้อมูลทั้ง Execution และจำนวน Queries ต่อ Resource.

54. Optimize Query Time กับ Query Count อะไรสำคัญกว่า

ขึ้นกับ Workload

ตัวอย่าง:

Query A
300 ms
เรียกวันละ 1 ครั้ง

Query B
3 ms
เรียกนาทีละหลายพันครั้ง

Query B อาจสร้าง Load รวมมากกว่า

ดังนั้นอย่าดูเฉพาะ Slow Query List

55. Prepare ไม่ควรถูกเรียกจาก Client โดยตรง

Database Logic ควรอยู่ฝั่ง Server Resource

Flow:

Client
↓
Server Event
↓
Server Validate
↓
MySQL.prepare
↓
Server Process
↓
Response ที่จำเป็น

Database Credentials และ Query Authority ไม่ควรถูกวางไว้ใน Client Script

56. Server Event ยังต้อง Validate หรือไม่

ต้อง

การใช้ Prepared Query ไม่ได้แปลว่า Player มี Permission

ตัวอย่าง:

Prepared SQL
=
Database Query Technique

Permission
=
Gameplay Security

Resource ยังต้องตรวจ:

Player
Identifier
Record Ownership
State
Permission

ก่อน Query ที่มีผลต่อข้อมูลสำคัญ

57. อย่าให้ Client เลือก Column เองโดยตรง

แม้ Prepare จะรองรับ:

??

Column Placeholder แต่ไม่ควรรับชื่อ Column จาก Client แล้วใช้โดยตรงโดยไม่มี Allowlist

แนวทางที่ปลอดภัยกว่า:

local allowedFields = {
    displayName = 'display_name',
    language = 'language'
}

แล้ว Server เลือก Column จาก Allowlist ที่กำหนดเอง

58. Dynamic Query จำเป็นจริงหรือไม่

หลายกรณีสามารถใช้ Functions แยก:

updateDisplayName()
updateLanguage()
updateTheme()

ทำให้ Queries ชัดเจนกว่า Dynamic Column SQL

Dynamic SQL ควรถูกใช้เมื่อมีเหตุผลด้าน Architecture จริงๆ

59. Prepare Function ควร Wrap ไว้ไหม

Resource ที่มี Query เดิมหลายจุดควรรวม Database Access Layer

ตัวอย่าง:

local DB = {}

function DB.getCharacter(identifier)

    return MySQL.prepare.await(
        [[
            SELECT
                `id`,
                `firstname`,
                `lastname`
            FROM `characters`
            WHERE `identifier` = ?
            LIMIT 1
        ]],
        {
            identifier
        }
    )

end

จากนั้น Resource อื่นเรียก:

local character =
    DB.getCharacter(identifier)

ช่วยไม่ให้ SQL ซ้ำหลายไฟล์

60. Database Layer มีข้อดีอะไร

ช่วยรวม:

SQL
Validation
Result Normalization
Error Handling

ไว้ที่เดียว

เวลาปรับ:

query
→ prepare

หรือเปลี่ยน SQL Structure ก็ไม่ต้องตามแก้ทุก Script

61. อย่า Prepare Query ที่ SQL เปลี่ยนตลอดเวลา

Prepare เหมาะที่สุดเมื่อ:

SQL Structure คงเดิม

และเปลี่ยนเฉพาะ Parameters

ถ้า Resourceสร้าง SQL String ใหม่ตลอด:

SELECT ...
WHERE condition A

SELECT ...
WHERE condition B

SELECT ...
WHERE condition C

อาจไม่ได้ประโยชน์ในรูปแบบเดียวกับ Query Structure ที่คงที่

62. FiveM Player Count สูงควรใช้ Prepare ทุก Query ไหม

ไม่

Player Count สูงทำให้ควร วัด Query Frequency และ Database Workload มากขึ้น

ไม่ได้หมายความว่า:

100 Players
=
Prepare ทุก SQL

เริ่มจาก Query ที่:

ถูกเรียกบ่อยที่สุด
+
มี Cost สูง

ก่อน

63. Query ตอน Player Join เป็น Candidate ที่ดีไหม

อาจเป็น Candidate เช่น:

Character Lookup
Account Lookup
Settings Lookup

เพราะ SQL Structure เหมือนกันกับผู้เล่นแต่ละคน

แต่ถ้า Load ครั้งเดียวตอน Login และ Server มี Player Join Rate ต่ำ ผลจาก Prepare อาจไม่ใหญ่

Benchmark ก่อนตัดสิน

64. Query Runtime ที่เรียกบ่อยเป็น Candidate ที่ดีกว่าไหม

มักน่าสนใจมากกว่า เช่น Query ที่เกิด:

บ่อยครั้งระหว่าง Gameplay

แต่ต้องถามต่อว่า:

Query นี้จำเป็นต้องเข้าฐานข้อมูลทุกครั้งหรือ Cache ได้?

ถ้า Cache ได้ การลด Query Frequency อาจช่วยมากกว่าการเปลี่ยน API

65. สูตรเลือก Query สำหรับ Prepare

ใช้ 5 ข้อ:

① Structure เดิมหรือไม่
② เรียกบ่อยหรือไม่
③ Parameters เปลี่ยนหรือไม่
④ Query จำเป็นต้องเข้าฐานข้อมูลหรือไม่
⑤ Benchmark แล้วได้ประโยชน์หรือไม่

ถ้าผ่านทุกข้อ MySQL.prepare เป็น Candidate ที่ดี

66. Checklist MySQL.prepare

ก่อนใช้ตรวจ:

  1. oxmysql ถูก Start แล้ว

  2. Import @oxmysql/lib/MySQL.lua

  3. Query Structure คงที่

  4. Query ถูกเรียกบ่อยจริง

  5. ใช้ ? สำหรับ Values

  6. ใช้ ?? เฉพาะเมื่อจำเป็น

  7. ไม่ใช้ Named Placeholder กับ Prepare

  8. ไม่ต่อ Client Input เป็น SQL

  9. เลือก Columns ที่จำเป็น

  10. ใช้ LIMIT เมื่อเหมาะสม

  11. ตรวจ Result Shape

  12. ตรวจ Date Result

  13. ตรวจ TINYINT/BIT Result

  14. ไม่ใช้ Prepare แทน Index

  15. ไม่ใช้ Prepare แทน Cache

  16. ไม่ใช้ Prepare แทน Transaction

  17. ไม่ Query ทุก Frame

  18. ตรวจ Query Count

  19. ตรวจ Query Time

  20. Benchmark Before/After

  21. ใช้ mysql_debug

  22. ใช้ Debug UI

  23. ตรวจ Slow Query

  24. ตรวจ Index

  25. ตรวจ Table Size

  26. ตรวจ Player Count

  27. Validate Server Events

  28. ตรวจ Record Ownership

  29. จำกัด Dynamic Columns

  30. ทดสอบ Resource Restart

ตาราง oxmysql Query APIs แบบเข้าใจง่าย

APIเหมาะกับ
MySQL.queryQuery ทั่วไป / หลาย Rows
MySQL.singleหนึ่ง Row
MySQL.scalarหนึ่งค่า
MySQL.insertInsert และรับ Insert ID
MySQL.updateUpdate และรับ affectedRows
MySQL.prepareQuery Structure เดิมที่เรียกบ่อย
MySQL.rawExecuteFrequent Query ที่ต้องการ Result Behavior แบบ RawExecute
MySQL.transactionหลาย Queries ที่ต้อง Commit พร้อมกัน

พฤติกรรมของแต่ละ API อ้างอิงจาก oxmysql Documentation ปัจจุบัน.

FiveM MySQL.prepare ใช้อย่างไร

ตัวอย่างพื้นฐาน:

local result =
    MySQL.prepare.await(
        [[
            SELECT
                `firstname`,
                `lastname`
            FROM `users`
            WHERE `identifier` = ?
        ]],
        {
            identifier
        }
    )

Official API ใช้รูปแบบนี้สำหรับ Lua Promise/Await.

FiveM Prepare ใช้ Placeholder แบบไหน

ใช้:

?

สำหรับ Value

และ:

??

สำหรับ Column

Named Placeholders ไม่รองรับใน Prepare ตาม Documentation ปัจจุบัน.

FiveM MySQL.prepare เร็วกว่า query ไหม

oxmysql ระบุว่า Prepare สามารถช่วย Execute Frequently-called Queries ได้เร็วขึ้น แต่ไม่สามารถรับประกันว่าจะเร็วกว่าในทุก Workload.

ความเร็วจริงขึ้นกับ Hardware, Database Settings, Version และ Current Workload.

ดังนั้นต้อง Benchmark

FiveM ควรใช้ Prepare ทุก Query ไหม

ไม่

ใช้เมื่อ:

Query Structure เดิม
+
ถูกเรียกบ่อย

ถ้า Query เรียกครั้งเดียวหรือไม่ใช่ Bottleneck การเปลี่ยนเป็น Prepare อาจไม่คุ้มกับความซับซ้อนที่เพิ่มขึ้น

FiveM Prepare แก้ Slow Query ได้ไหม

ช่วยได้เฉพาะบางกรณีของ Query ที่ถูกเรียกบ่อย

ถ้า Slow เพราะ:

Table Scan
Index ไม่เหมาะ
Result ใหญ่
Database Server ช้า

ต้องแก้ต้นเหตุนั้นด้วย

Prepare ไม่ได้สร้าง Index หรือ Rewrite SQL ให้อัตโนมัติ

FiveM Prepare ใช้กับ SELECT ได้ไหม

ได้ และ Official Documentation มี SELECT Example โดยตรง.

แต่ต้องเข้าใจ Result Shape ซึ่งสามารถต่างตามจำนวน Columns และ Rows

FiveM Prepare ใช้กับ UPDATE ได้ไหม

สามารถ Execute SQL Structure ที่เหมาะสมได้ แต่ถ้าต้องการ affectedRows อย่างชัดเจน MySQL.update มี API Contract สำหรับเรื่องนี้โดยตรง.

FiveM Prepare ใช้กับ INSERT ได้ไหม

ได้ใน Query Structure ที่เหมาะสม แต่หากต้องการ Insert ID โดยตรง MySQL.insert ถูกออกแบบมาสำหรับกรณีนั้น.

FiveM Prepare ใช้กับ Upsert ได้ไหม

ได้ และ Documentation หลักของ oxmysql ใช้ MySQL.prepare ในตัวอย่าง INSERT ... ON DUPLICATE KEY UPDATE.

FiveM Prepare ใช้กับ Transaction ได้ไหม

เป็นคนละ API และแก้คนละปัญหา

Prepare:

Repeated Query

Transaction:

Atomic Multiple Queries

Transaction จะ Commit เมื่อทั้งหมดสำเร็จ.

FiveM Prepare ยังต้องใช้ Parameterized Query ไหม

ต้อง

Prepare ใช้ ?/?? Placeholders และ oxmysql ระบุว่า Placeholder Parameters ช่วยลด SQL Injection Methods ทั่วไป.

FAQ FiveM MySQL.prepare

MySQL.prepare คืออะไร

เป็น oxmysql API สำหรับ Execute Frequently-called Queries ด้วย Prepared Statements และรองรับ Parameter Sets สำหรับ Query เดียว.

MySQL.prepare.await ใช้ได้ไหม

ได้ เป็น Promise/Await API ที่ Official Documentation รองรับโดยตรง.

Prepare ใช้ ? ได้ไหม

ได้ ? ใช้สำหรับ Value Placeholder.

Prepare ใช้ ?? ได้ไหม

ได้ ?? ใช้สำหรับ Column Placeholder.

Prepare ใช้ @identifier ได้ไหม

Named Placeholders ไม่รองรับใน Prepare และ oxmysql ยังระบุ Named Placeholders ใน API ทั่วไปว่า Deprecated.

Prepare เร็วกว่าทุก Query ไหม

ไม่สามารถรับประกันได้ แม้ Documentation ระบุว่าเหมาะสำหรับ Queries ที่ถูกเรียกบ่อย แต่ Performance จริงขึ้นกับ Environment และ Workload.

Prepare แทน Index ได้ไหม

ไม่ได้ Prepared Statement กับ Database Index ทำหน้าที่ต่างกัน

Prepare แทน Cache ได้ไหม

ไม่ได้ Prepare ยังคงเข้าฐานข้อมูล ส่วน Cache สามารถลดจำนวน Database Queries ได้

Prepare กับ Query ต่างกันอย่างไร

MySQL.query เป็น General Query API ส่วน MySQL.prepare เน้น Query ที่ถูกเรียกบ่อยด้วย Prepared Statement.

Prepare กับ Transaction ต่างกันอย่างไร

Prepare เน้น Repeated Query ส่วน Transaction ทำให้หลาย Queries Commit เมื่อทั้งหมดสำเร็จ.

Prepare SELECT คืน Array เสมอไหม

ไม่ oxmysql ระบุว่า Result สามารถเป็น Column, Row หรือ Array of Rows ตามจำนวน Columns/Rows ที่เลือก.

Prepare รองรับหลาย Parameter Sets ไหม

รองรับ โดยเป็นหนึ่งในความสามารถที่ Official Documentation ระบุไว้.

ประเด็นสำคัญ

FiveM MySQL.prepare เหมาะกับ Query ที่มีลักษณะ:

SQL Structure เดิม
+
ถูกเรียกบ่อย
+
Parameters เปลี่ยน

โดย oxmysql ระบุว่า Prepare สามารถช่วยให้ Frequently-called Queries Execute ได้เร็วขึ้น และรองรับ Parameter Sets หลายชุดสำหรับ Query เดียว.

สำหรับ Query ใหม่ควรใช้:

?

เป็น Value Placeholder และ:

??

เฉพาะเมื่อจำเป็นต้องใช้ Column Placeholder ส่วน Named Placeholder ไม่รองรับกับ prepare และ oxmysql ระบุ Named Parameters แบบเก่าว่า Deprecated.

แต่ต้องจำว่า:

Prepare
≠
Index

Prepare
≠
Cache

Prepare
≠
Transaction

Prepare
≠
แก้ Slow Query ทุกกรณี

หาก Query ยังช้า ให้ตรวจ SQL Structure, Index, Query Count และ Database Workload ต่อ และใช้ mysql_debug หรือ oxmysql Debug UI วัดผลจริง เพราะ Performance แตกต่างตาม Hardware, Database Settings, Database Version และ Current Workload.

สำหรับผู้อ่าน comsiam ให้จำสูตร “Query ซ้ำบ่อย → Prepare → วัดผล” และ comsiam แนะนำให้เริ่ม Optimize จาก Query ที่มี Frequency สูงจริงก่อน อย่าเปลี่ยน SQL ทั้ง Server เป็น MySQL.prepare พร้อมกัน เพราะการลด Query Count, ใช้ Cache, ปรับ Index หรือแก้ Query Structure อาจให้ผลมากกว่าในแต่ละ Bottleneck

Comments

Popular posts from this blog

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

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

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