FiveM MySQL Transaction คืออะไร? วิธีใช้ oxmysql Transaction ให้หลาย Query สำเร็จหรือย้อนกลับพร้อมกัน

FiveM MySQL Transaction คือวิธีรวม SQL หลายคำสั่งให้ทำงานเป็นชุดเดียว โดย oxmysql ระบุว่า Transaction จะ Commit ก็ต่อเมื่อทุก Query สำเร็จ และหาก Query ใด Query หนึ่งล้มเหลว Queries ใน Transaction นั้นจะไม่ถูก Commit.

แนวคิดนี้สำคัญมากกับ FiveM Server ที่ Action หนึ่งต้องแก้ข้อมูลหลาย Tables เพราะถ้าใช้ Queries แยกกันแล้ว Query กลางทางล้มเหลว อาจเกิดข้อมูล “สำเร็จครึ่งเดียว” เช่น Character ถูกสร้างแล้วแต่ Settings ไม่ถูกสร้าง หรือข้อมูลใน Table หนึ่งเปลี่ยนแต่ Table ที่เกี่ยวข้องไม่เปลี่ยนตาม

จำหลักง่ายๆ:

หลาย Query
+
ต้องสำเร็จเป็นชุดเดียว
=
Transaction

① FiveM Transaction คืออะไร

สมมติ Action หนึ่งต้องทำ:

Query A
↓
Query B
↓
Query C

หากใช้ Transaction:

A สำเร็จ
B สำเร็จ
C สำเร็จ
↓
COMMIT

แต่ถ้า:

A สำเร็จ
B ล้มเหลว
C ไม่สำเร็จ
↓
ไม่ Commit ชุด Transaction

นี่คือ Behavior หลักที่ oxmysql ระบุไว้สำหรับ MySQL.transaction.

② Commit คืออะไร

Commit หมายถึงการยืนยันการเปลี่ยนแปลงของ Transaction ลง Database

Flow:

BEGIN
↓
Query 1
↓
Query 2
↓
Query 3
↓
ทุกอย่างสำเร็จ
↓
COMMIT

ใน oxmysql Developer ไม่จำเป็นต้องเขียน BEGIN และ COMMIT เองเมื่อใช้ MySQL.transaction() เพราะ Resource จัดการ Transaction Flow ให้ตาม API.

③ Rollback คืออะไร

แนวคิด Rollback คือไม่ยืนยันการเปลี่ยนแปลงของ Transaction เมื่อชุด Queries ไม่สามารถสำเร็จครบ

ในระดับ API oxmysql อธิบายว่า หากหนึ่ง Query ล้มเหลว ไม่มี Query ใน Transaction นั้นถูก Commit.

จึงช่วยป้องกันสถานะ:

Table A เปลี่ยน
แต่
Table B ไม่เปลี่ยน

เมื่อสองการเปลี่ยนแปลงควรเกิดพร้อมกัน

④ MySQL.transaction คืนค่าอะไร

oxmysql ระบุว่า Return Value เป็น:

boolean

ดังนั้น:

local success =
    MySQL.transaction.await(queries)

if success then
    print('Transaction successful')
else
    print('Transaction failed')
end

สามารถใช้ตรวจว่า Transaction สำเร็จหรือไม่.

⑤ Syntax พื้นฐานของ MySQL.transaction.await

ตัวอย่าง:

local queries = {
    {
        query = [[
            INSERT INTO `profiles`
                (`identifier`, `display_name`)
            VALUES (?, ?)
        ]],
        values = {
            identifier,
            displayName
        }
    },

    {
        query = [[
            INSERT INTO `profile_settings`
                (`identifier`, `theme`)
            VALUES (?, ?)
        ]],
        values = {
            identifier,
            'default'
        }
    }
}

local success =
    MySQL.transaction.await(queries)

print(success)

oxmysql รองรับ Specific Format ซึ่งแต่ละ Query มี Parameters ของตัวเองแบบนี้.

⑥ Specific Format คืออะไร

รูปแบบหนึ่งที่ oxmysql รองรับคือ:

local queries = {
    {
        query = 'INSERT INTO `table_a` (`id`) VALUES (?)',
        values = { 1 }
    },

    {
        query = 'INSERT INTO `table_b` (`id`, `name`) VALUES (?, ?)',
        values = {
            2,
            'Example'
        }
    }
}

แต่ละ Query จึงมี:

SQL ของตัวเอง
+
Parameters ของตัวเอง

Official Documentation เรียกรูปแบบนี้ว่า Specific Format.

⑦ Array Format ใช้ได้ไหม

ได้

oxmysql ยังรองรับ:

local queries = {
    {
        'INSERT INTO `table_a` (`id`) VALUES (?)',
        { 1 }
    },

    {
        'INSERT INTO `table_b` (`id`, `name`) VALUES (?, ?)',
        {
            2,
            'Example'
        }
    }
}

local success =
    MySQL.transaction.await(queries)

เป็นอีก Syntax ที่ Documentation รองรับ.

⑧ Shared Parameters คืออะไร

oxmysql ยังมี Shared Format ที่สามารถส่งชุด Queries พร้อม Parameters ร่วมกันได้ โดย Documentation รองรับ Transaction Formats หลายแบบ.

แต่สำหรับ Resource ใหม่ที่แต่ละ Query มี Parameters ต่างกัน การใช้ Specific Format มักอ่านง่ายกว่า เช่น:

Query
+
Values

Query
+
Values

เห็นความสัมพันธ์ของ Parameters กับ SQL ชัดเจน

⑨ Transaction เหมาะใช้เมื่อไหร่

ใช้เมื่อหลาย Operations มีความสัมพันธ์กันและไม่ควรเกิดเพียงบางส่วน

ตัวอย่าง:

สร้าง Character
+
สร้าง Character Settings

หรือ:

สร้าง Persistent Record
+
สร้าง Related Metadata

ถ้า Query แรกสำเร็จแต่ Query หลังล้มเหลว ข้อมูลอาจไม่ครบ

Transaction ช่วยป้องกันปัญหานี้ด้วยการ Commit เมื่อทุก Query สำเร็จเท่านั้น.

⑩ Transaction ไม่จำเป็นกับ Query เดียว

ถ้ามีเพียง:

UPDATE `profiles`
SET `display_name` = ?
WHERE `id` = ?;

Query เดียวโดยทั่วไปไม่จำเป็นต้องห่อ MySQL.transaction() เพียงเพื่อเรียก Update หนึ่งครั้ง

ใช้:

local affected =
    MySQL.update.await(
        [[
            UPDATE `profiles`
            SET `display_name` = ?
            WHERE `id` = ?
        ]],
        {
            displayName,
            profileId
        }
    )

MySQL.update คืนจำนวน Rows ที่ได้รับผล.

⑪ Transaction ไม่ได้หมายถึง Batch ทุกอย่าง

อย่าคิดว่า:

มี 100 Query
↓
ต้อง Transaction ทั้งหมด

ถามก่อนว่า Queries เหล่านั้นต้องมี Atomic Relationship กันหรือไม่

ถ้าทำงานแยกอิสระกัน การบังคับให้อยู่ Transaction ใหญ่เดียวอาจไม่จำเป็น

⑫ Atomic หมายถึงอะไร

ในบริบทนี้ให้จำง่ายๆ ว่า:

ทั้งหมด
หรือ
ไม่มีเลย

ถ้า Action ต้องมี:

Record A
+
Record B
+
Record C

พร้อมกัน

Transaction เหมาะกับ Requirement นี้

⑬ ตัวอย่างสร้าง Character แบบ Transaction

สมมติ Schema มี:

characters
character_settings

สามารถเขียน:

local queries = {
    {
        query = [[
            INSERT INTO `characters`
                (`identifier`, `firstname`, `lastname`)
            VALUES (?, ?, ?)
        ]],
        values = {
            identifier,
            firstName,
            lastName
        }
    },

    {
        query = [[
            INSERT INTO `character_settings`
                (`identifier`, `theme`)
            VALUES (?, ?)
        ]],
        values = {
            identifier,
            'default'
        }
    }
}

local success =
    MySQL.transaction.await(queries)

if not success then
    print('Character transaction failed')
    return
end

Transaction จะคืน Boolean บอกผลของชุด Queries.

⑭ ข้อจำกัดของตัวอย่าง Character ด้านบน

ตัวอย่างนั้นใช้ identifier เชื่อม Records เพื่อให้ Code อ่านง่าย

ใน Production Schema จริงอาจใช้:

character_id
Foreign Key
Internal UUID

แทน

ต้องออกแบบตาม Framework และ Database Schema ของ Server จริง

⑮ ถ้าต้องใช้ Insert ID จาก Query แรกทำอย่างไร

นี่เป็นเรื่องที่ต้องวาง Flow ให้ดี

MySQL.insert ปกติสามารถคืน Insert ID ของ Row ที่สร้าง.

แต่ Transaction API คืนค่า Boolean ของ Transaction ไม่ได้คืน Insert ID ของแต่ละ Queryโดยตรงในรูปแบบเดียวกับ MySQL.insert.

ดังนั้นหาก Queries ภายใน Transaction ต้องสัมพันธ์กับ ID ที่สร้างใหม่ ต้องออกแบบ SQL/Schema ให้รองรับ Dependency นั้นอย่างชัดเจน

⑯ LAST_INSERT_ID ใช้ได้ไหม

Official oxmysql Benchmark มีตัวอย่าง Transaction ที่:

INSERT INTO `test_table` ...

ตามด้วย:

UPDATE `test_table`
SET `username` = ?
WHERE `id` = LAST_INSERT_ID()

ภายใน Transaction เดียวกัน.

นี่แสดงว่า Workflow ที่อาศัย LAST_INSERT_ID() สามารถถูกใช้ภายใน Transaction ตาม Connection Context ของตัวอย่าง oxmysql ได้

⑰ ตัวอย่าง LAST_INSERT_ID

local queries = {
    {
        query = [[
            INSERT INTO `profiles`
                (`identifier`)
            VALUES (?)
        ]],
        values = {
            identifier
        }
    },

    {
        query = [[
            UPDATE `profiles`
            SET `display_name` = ?
            WHERE `id` = LAST_INSERT_ID()
        ]],
        values = {
            displayName
        }
    }
}

local success =
    MySQL.transaction.await(queries)

แนวทางนี้อิง Pattern ที่ oxmysql Benchmark ใช้ใน Transaction Example.

⑱ Transaction กับ MySQL.insert ต่างกันอย่างไร

MySQL.insert

เหมาะเมื่อ:

เพิ่มหนึ่ง Record
+
ต้องการ Insert ID

Official API คืน Insert ID หากถูกต้อง.

MySQL.transaction

เหมาะเมื่อ:

หลาย Query
+
ต้องสำเร็จครบ

และ Return Value เป็น Boolean.

⑲ Transaction กับ MySQL.update ต่างกันอย่างไร

MySQL.update:

ทำ Query Update
↓
คืน affectedRows

Transaction:

หลาย Query
↓
คืน success true/false

เลือก API ให้ตรงกับสิ่งที่ต้องการรู้จาก Result

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

Transaction

เน้น:

Data Consistency

Prepare

oxmysql ระบุว่าเหมาะกับ Query ที่ถูกเรียกบ่อยและสามารถ Execute Frequently-called Queries ได้เร็วขึ้น.

ดังนั้น:

Transaction
≠
Prepare

หนึ่งตัวแก้เรื่อง Atomicity อีกตัวเกี่ยวกับ Execution ของ Query ที่เรียกซ้ำ

㉑ Transaction ทำให้ Query เร็วขึ้นไหม

ไม่ควรใช้ด้วยเหตุผลนี้เป็นหลัก

หน้าที่ของ Transaction คือให้หลาย Queries Commit เมื่อทั้งหมดสำเร็จ.

Performance ต้องวัดแยก เพราะ oxmysql ระบุว่า Query Speeds แตกต่างตาม:

Hardware
Database Settings
Database Version
Current Workload

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

ไม่

Transaction ควรมี Scope เท่าที่จำเป็น

ถ้า Action ต้องแก้เพียง:

Table A
+
Table B

ก็ไม่จำเป็นต้องลาก Queries ที่ไม่เกี่ยวข้องอีก 30 ตัวเข้ามาเพียงเพราะเกิดในช่วงเวลาเดียวกัน

Transaction ที่อ่านง่ายควรสะท้อน Business Operation หนึ่งชุด

㉓ Transaction Isolation Level คืออะไร

oxmysql รองรับ ConVar:

mysql_transaction_isolation_level

โดยรับค่าตัวเลข:

1–4

และ Documentation ปัจจุบันระบุ Default เป็น:

2

㉔ Isolation Level 1 คืออะไร

oxmysql Mapping ปัจจุบันระบุ:

1
=
Repeatable Read

㉕ Isolation Level 2 คืออะไร

2
=
Read Committed

และเป็น Default ที่ oxmysql Documentation ปัจจุบันระบุ.

㉖ Isolation Level 3 คืออะไร

3
=
Read Uncommitted

ตาม Mapping ของ oxmysql.

㉗ Isolation Level 4 คืออะไร

4
=
Serializable

ตาม Transaction Documentation ปัจจุบัน.

㉘ ตาราง Isolation Level ของ oxmysql

ค่าIsolation Level
1Repeatable Read
2Read Committed
3Read Uncommitted
4Serializable

Default ปัจจุบันคือ 2 หรือ Read Committed.

㉙ ควรเปลี่ยน Isolation Level ไหม

ถ้ายังไม่มีเหตุผลด้าน Database Concurrency ที่ชัดเจน ไม่ควรเปลี่ยนเพียงเพราะเห็นว่าระดับเลขสูงกว่า

Isolation Level มี Trade-off และเกี่ยวข้องกับ Behavior ของ Concurrent Transactions

ถ้า Server ปัจจุบันทำงานปกติ ควรเริ่มจาก Default ของ oxmysql และเปลี่ยนเฉพาะเมื่อเข้าใจผลต่อ Workload จริง.

㉚ วิธีตั้ง Transaction Isolation Level

ตัวอย่าง:

set mysql_transaction_isolation_level 2

ค่าปัจจุบัน 2 หมายถึง Read Committed ตาม oxmysql Mapping.

อย่า Copy ค่าอื่นจาก Server คนอื่นโดยไม่เข้าใจเหตุผล

㉛ Transaction ช่วยเรื่อง Concurrent Requests ไหม

Transaction ช่วยรักษาความเป็นชุดของ Database Operations แต่ไม่ได้หมายความว่า Application Logic จะปลอดจาก Race Condition ทุกประเภทโดยอัตโนมัติ

FiveM Server ยังต้องตรวจ:

Current State
Permission
Record Ownership
Request Validity

ก่อนเริ่ม Database Operation

Cfx.re แนะนำให้ Client-triggered Network Events ถูกตรวจด้วยข้อมูลฝั่ง Server ไม่ให้ Client เป็นผู้กำหนดค่าที่สำคัญเอง.

㉜ Client ขอ Database Action ควรทำอย่างไร

Architecture:

Client
↓
Request Action
↓
Server Validate
↓
Load/Check Server State
↓
Transaction
↓
Success / Failure
↓
Server ส่งผลกลับ

ไม่ควร:

Client ส่งข้อมูล
↓
Database UPDATE ทันที

โดยไม่มี Validation

㉝ ตัวอย่าง Server Event ที่ควรหลีกเลี่ยง

RegisterNetEvent(
    'profile:update',
    function(profileId, newOwner)

        MySQL.update.await(
            [[
                UPDATE `profiles`
                SET `owner` = ?
                WHERE `id` = ?
            ]],
            {
                newOwner,
                profileId
            }
        )

    end
)

ปัญหาคือ Client เป็นผู้ส่งทั้ง:

profileId
newOwner

และ Server ไม่ตรวจสิทธิ์ก่อน

Cfx.re เตือนว่าข้อมูลจาก Network Event ควรถูกตรวจด้วย Server-side Methods.

㉞ ตัวอย่าง Server Validation ที่ดีกว่า

RegisterNetEvent(
    'profile:updateName',
    function(profileId, displayName)

        local src = source

        if type(profileId) ~= 'number' then
            return
        end

        if type(displayName) ~= 'string' then
            return
        end

        if #displayName < 1
            or #displayName > 40 then
            return
        end

        local identifier =
            GetPlayerIdentifierByType(
                src,
                'license'
            )

        if not identifier then
            return
        end

        local affected =
            MySQL.update.await(
                [[
                    UPDATE `profiles`
                    SET `display_name` = ?
                    WHERE `id` = ?
                      AND `identifier` = ?
                ]],
                {
                    displayName,
                    profileId,
                    identifier
                }
            )

        if affected == 0 then
            return
        end
    end
)

ตัวอย่างนี้ตรวจ Input และยืนยัน Ownership ผ่านข้อมูล Server/Database มากขึ้น ซึ่งสอดคล้องกับ Cfx.re Server Event Security Guidance.

㉟ Transaction ยังต้อง Validate Client ไหม

ต้อง

Transaction รับประกันเรื่อง Database Operations เป็นชุด

ไม่ได้รับประกันว่า:

Player มีสิทธิ์
Request ถูกต้อง
ข้อมูล Client จริง

ดังนั้น:

Security Validation
≠
Transaction

เป็นคนละ Layer

㊱ Transaction กับ SQL Injection

Transaction ไม่ได้แทน Parameterized Query

ยังควรเขียน:

{
    query = [[
        UPDATE `profiles`
        SET `display_name` = ?
        WHERE `id` = ?
    ]],
    values = {
        displayName,
        profileId
    }
}

แทนการเอา User Input ต่อเป็น SQL String โดยตรง

㊲ อย่าต่อ Client Input เข้า Query String

หลีกเลี่ยง:

local sql =
    "UPDATE profiles SET display_name = '" ..
    displayName ..
    "' WHERE id = " ..
    profileId

ใช้ Placeholders:

UPDATE `profiles`
SET `display_name` = ?
WHERE `id` = ?

และส่ง Parameters แยกจาก Query Structure

㊳ Transaction Failed ต้องทำอะไร

เมื่อ:

local success =
    MySQL.transaction.await(queries)

if not success then
    -- handle failure
end

ควรมี Failure Handling เช่น:

Log Error
ไม่เปลี่ยน Runtime State เป็น Success
ไม่ส่ง Success ให้ Client
Retry เฉพาะเมื่อ Architecture รองรับ

อย่าทำเหมือน Transaction สำเร็จหาก Return Value เป็น false.

㊴ อย่า Update Runtime State ก่อน Transaction สำเร็จ

ตัวอย่างที่ควรระวัง:

เปลี่ยน Cache ก่อน
↓
Transaction Database
↓
Transaction ล้มเหลว
↓
Cache กับ Database ไม่ตรง

Pattern ที่ปลอดภัยกว่าในหลายระบบ:

Validate
↓
Transaction
↓
Success
↓
Update Runtime State

หรือมี Rollback Strategy สำหรับ Runtime State ให้ชัดเจน

㊵ Database สำเร็จแต่ส่ง Event กลับ Client ไม่ได้ทำอย่างไร

ต้องแยก:

Database Transaction

ออกจาก:

Network Delivery

Transaction สามารถ Commit สำเร็จ แต่ Client อาจ Disconnect หลังจากนั้น

ดังนั้น Persistent State ควรยึด Database/Server State เป็นหลัก ไม่ควรถือว่า Client Acknowledgement เป็นเงื่อนไขของ Database Commit เสมอไป

㊶ Player Disconnect ระหว่าง Transaction

Server-side Database Operation อาจดำเนินต่อใน Context ของ Resourceแม้ Player Connection State เปลี่ยน

ดังนั้นหลัง Await หากต้องส่งข้อมูลกลับ Client ควรพิจารณาว่า Source ยัง Valid สำหรับ Action ต่อหรือไม่

แต่ Database Consistency ไม่ควรผูกกับการที่ Client ต้องออนไลน์ตลอดช่วง Operation

㊷ Transaction กับ Player Save

ถ้าการ Save Player หนึ่งครั้งต้อง Update:

profile
settings
statistics

และทั้งสามต้องสอดคล้องกัน Transaction อาจเหมาะ

แต่ถ้าแต่ละ Table เป็น Independent Data ที่ Save แยกได้ ก็ไม่จำเป็นต้องบังคับ Transaction เดียวเสมอ

ออกแบบตาม Consistency Requirement

㊸ Transaction กับ Character Creation

Character Creation เป็น Use Case ที่เข้าใจง่าย:

Character
↓
Default Settings
↓
Profile Metadata

ถ้าต้องมีทั้งหมดก่อนถือว่า Character พร้อมใช้ Transaction ช่วยไม่ให้เหลือ Character Record ที่สร้างไม่ครบ

㊹ Transaction กับ Delete Data

ถ้า Resource ต้องลบ Related Records หลาย Table:

profile_settings
↓
profile_statistics
↓
profiles

และต้องลบสำเร็จเป็นชุดเดียว Transaction สามารถเหมาะได้

แต่อีกแนวทางหนึ่งคือใช้ Database Constraints/Foreign Key Cascades ตาม Schema Design

ต้องเลือกให้ตรง Framework และ Database Architecture

㊺ Transaction กับ Schema Migration

ไม่ควรสมมติว่า Database DDL ทุกประเภทจะมี Transaction Behavior เหมือน Data Manipulation Queries

Schema Migration ควรถูกออกแบบและทดสอบแยกจาก Gameplay Transaction

ก่อน Migration Production ต้อง:

Backup
Test
อ่าน Migration Notes

เสมอ

㊻ Transaction กับ Slow Query

Transaction ไม่ได้แก้ Slow Query โดยอัตโนมัติ

หาก Query ภายใน Transaction ช้า:

Query A = 10 ms
Query B = 500 ms
Query C = 20 ms

ต้อง Optimize Query B ตามปกติ เช่น:

EXPLAIN
Index
Query Design
Workload

Transaction เพียงทำให้ Operations เป็นชุด

㊼ Transaction ใหญ่เกินไปอาจมีผลอย่างไร

ยิ่ง Transaction มีงานจำนวนมาก ก็ยิ่งควรระวัง:

เวลาที่ Transaction เปิดอยู่
จำนวน Rows ที่แตะ
Concurrency
Query Time
Failure Probability

จึงควรรวมเฉพาะ Operations ที่ต้อง Atomic จริง

㊽ Query Performance วัดอย่างไร

oxmysql ระบุว่า Real Query Speeds สามารถดูผ่าน Debug UI และ Server Console เมื่อเปิด mysql_debug และความเร็วแตกต่างตาม Hardware, Database Settings, Version และ Workload.

ดังนั้นเมื่อ Transaction ช้า:

mysql_debug
↓
ดู Query ภายใน
↓
หา Query ที่กินเวลา

แทนการสรุปว่า Transaction API ช้าเอง

㊾ Transaction ไม่ควรถูกใช้แทน Error Handling

แม้ Transaction จะป้องกัน Partial Commit แต่ Resource ยังต้อง:

Check success
Log failure
Handle client response
Recover runtime state

ด้วยตัวเอง

ตัวอย่าง:

local success =
    MySQL.transaction.await(queries)

if not success then
    print(
        '[myresource] database transaction failed'
    )

    return false
end

return true

㊿ Transaction Callback ใช้ได้ไหม

ได้

oxmysql รองรับ Callback Style เช่น:

MySQL.transaction(
    queries,
    function(success)

        print(success)

    end
)

นอกจาก Promise/Await Style.

51. Await กับ Callback เลือกแบบไหนดี

ขึ้นกับ Code Style และ Resource Architecture

Await

local success =
    MySQL.transaction.await(queries)

อ่าน Flow ต่อเนื่องง่าย

Callback

MySQL.transaction(
    queries,
    function(success)
        -- next step
    end
)

ทั้งสองแบบได้รับการรองรับโดย oxmysql.

52. ตัวอย่าง Transaction Function ที่นำกลับใช้ได้

local function createProfile(
    identifier,
    displayName
)
    local queries = {
        {
            query = [[
                INSERT INTO `profiles`
                    (`identifier`, `display_name`)
                VALUES (?, ?)
            ]],
            values = {
                identifier,
                displayName
            }
        },

        {
            query = [[
                INSERT INTO `profile_settings`
                    (`identifier`, `theme`)
                VALUES (?, ?)
            ]],
            values = {
                identifier,
                'default'
            }
        }
    }

    local success =
        MySQL.transaction.await(queries)

    return success == true
end

ฟังก์ชันนี้ทำให้ Call Site อ่านง่าย:

if not createProfile(
    identifier,
    displayName
) then
    return
end

53. ป้องกัน Duplicate Request อย่างไร

Transaction ไม่ได้หยุด Client จาก Trigger Event ซ้ำโดยอัตโนมัติ

Resource ควรมี:

Server-side State
Unique Constraint
Request Lock
หรือ
Application Validation

ตาม Use Case

เช่น Character Creation ควรตรวจ Server-side ว่า Player ยังอยู่ใน Creation State ที่ถูกต้อง และ Database Schema อาจใช้ Unique Constraint ในข้อมูลที่ต้องไม่ซ้ำ

54. Unique Constraint สำคัญกับ Transaction ไหม

ช่วยป้องกัน Duplicate Data ในระดับ Database

หาก Query หนึ่งใน Transaction ละเมิด Constraint และล้มเหลว Transaction ก็จะไม่ Commit ทั้งชุดตาม Behavior ที่ oxmysql ระบุ.

นี่ช่วยให้ Database Schema ทำงานร่วมกับ Application Validation ได้

55. Transaction ไม่แทน Unique Constraint

อย่าใช้ Code อย่างเดียวเพื่อรับประกัน Unique Data

ถ้า Business Rule ระบุ:

ค่าบางอย่างห้ามซ้ำ

Database Constraint ควรถูกพิจารณาด้วย เพราะ Concurrent Requests อาจเกิดใกล้กันมาก

Application Validation และ Database Constraints จึงควรเสริมกัน

56. Race Condition คืออะไรใน FiveM Database

ตัวอย่าง:

Request A
ตรวจว่า Record ยังไม่มี
↓
Request B
ตรวจว่า Record ยังไม่มี
↓
A Insert
↓
B Insert

หากไม่มี Constraint/Concurrency Design อาจได้ Duplicate Records

Transaction อย่างเดียวไม่จำเป็นต้องแก้ Race Pattern นี้ทุกแบบ

ต้องออกแบบ Database Constraints และ Query Flow เพิ่ม

57. Transaction กับ Upsert ใช้แทนกันไหม

ไม่ทั้งหมด

Upsert

เหมาะกับ:

Insert ถ้ายังไม่มี
หรือ
Update ถ้ามี

Transaction

เหมาะกับ:

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

บางระบบสามารถใช้ Upsert ภายใน Transaction ได้ หากตรงกับ Requirement จริง

58. Transaction กับ Prepared Query ใช้ร่วมกันอย่างไร

Prepare มี API แยกสำหรับ Frequently-called Query และ Transaction มี API สำหรับ Multiple Queries.

ไม่ควรพยายามรวมทุก Optimization Technique โดยอัตโนมัติ

วัดก่อนว่า Bottleneck คือ:

Query Frequency
Database Access
หรือ
Consistency

59. Transaction Isolation Level ควรเหมือนทุก Server ไหม

ไม่จำเป็น

oxmysql มี Default 2 หรือ Read Committed แต่เปิดให้ปรับตั้งแต่ 1–4.

Server ที่ต้องการเปลี่ยนควรเข้าใจ Database Concurrency และทดสอบ Workload จริงก่อน

60. Checklist ก่อนใช้ MySQL.transaction

ตรวจให้ครบ:

  1. มีมากกว่าหนึ่ง Query จริงหรือไม่

  2. Queries ต้องสำเร็จพร้อมกันหรือไม่

  3. ถ้า Query หนึ่งล้มเหลว อีก Query ควรถูกยกเลิกหรือไม่

  4. ใช้ Parameters แยกจาก SQL

  5. Validate Client Input แล้วหรือไม่

  6. ตรวจ Player Permission แล้วหรือไม่

  7. ตรวจ Record Ownership แล้วหรือไม่

  8. Query แต่ละตัวมี Index ที่เหมาะสมหรือไม่

  9. Transaction ใหญ่เกินไปหรือไม่

  10. Runtime State เปลี่ยนก่อน Commit หรือไม่

  11. มี Failure Handling หรือไม่

  12. มี Error Logging หรือไม่

  13. Client Disconnect แล้ว Flow ยังถูกหรือไม่

  14. มี Duplicate Request Protection หรือไม่

  15. มี Unique Constraints ที่จำเป็นหรือไม่

  16. Query Count สูงหรือไม่

  17. Transaction ถูกเรียกทุก Tick หรือไม่

  18. Slow Query อยู่ใน Transaction หรือไม่

  19. เปิด mysql_debug เมื่อต้องวิเคราะห์หรือไม่

  20. ทดสอบ Success Path แล้วหรือไม่

  21. ทดสอบ Failure Path แล้วหรือไม่

  22. ทดสอบ Query ที่สองล้มเหลวหรือยัง

  23. ตรวจว่า Query แรกไม่ถูก Commit เมื่อ Query หลังล้มเหลว

  24. ตรวจ Database หลัง Resource Restart

  25. Backup ก่อนเปลี่ยน Schema

61. ตัวอย่างทดสอบ Transaction Failure

ใน Development Database สามารถสร้าง Scenario ที่ Query หลังล้มเหลวโดยตั้งใจ เพื่อยืนยันว่า Query ก่อนหน้าไม่ถูก Commit

แนวคิด:

Transaction
↓
Query 1 Valid
↓
Query 2 Invalid
↓
success = false
↓
ตรวจ Database
↓
Query 1 ต้องไม่ถูก Commit

Behavior นี้ตรงกับ Guarantee ที่ oxmysql ระบุสำหรับ Transaction.

62. อย่าทดสอบ Failure บน Production Data

การทดสอบ Transaction Failure ควรทำใน:

Development Database
หรือ
Staging

ไม่ใช่ใช้ Player Data จริง

โดยเฉพาะ Query ประเภท Delete/Update

63. Transaction กับ Backup ยังต้องมีไหม

ต้องมี

Transaction ป้องกัน Partial Commit ของ Operation หนึ่งชุด

Backup ป้องกันปัญหาอีกระดับ เช่น:

Schema Error
Resource Bug
Admin Error
Database Corruption
Migration ผิด

จึงไม่ใช่ระบบที่แทนกัน

64. Transaction กับ Resource Restart

ถ้า Transaction สำเร็จและ Commit แล้ว Persistent Data ยังคงอยู่ใน Databaseตามปกติ

ส่วน Lua Cache/Runtime Table ของ Resource จะ Reset เมื่อ Resource Restart

จึงต้องแยก:

Database Commit
=
Persistent

Lua Runtime State
=
Temporary

65. Transaction กับ Server Restart

เช่นเดียวกัน เมื่อ Transaction Commit สำเร็จ ข้อมูลนั้นเป็น Database State

เมื่อ FXServer Restart Resource ควร Load Persistent Data กลับมาตาม Architecture

Transaction ไม่ได้ Persist Lua Variables หรือ Entity Handles

66. Transaction กับ State Bag

State Bag:

Runtime Network State

Transaction:

Persistent Database Consistency

ตัวอย่าง Architecture:

Client Request
↓
Server Validate
↓
Database Transaction
↓
Success
↓
Update Server Cache
↓
Update State Bag
↓
Clients เห็น State ใหม่

ช่วยแยก Persistent Layer กับ Runtime Network Layer ให้ชัดเจน

67. Transaction กับ Entity Creation

อย่าผูก Database Transaction กับ Runtime Entity มากเกินไปจนแยก Recovery ไม่ได้

ตัวอย่าง Persistent Vehicle:

Database Record
↓
Commit
↓
Server สร้าง Runtime Entity

ถ้า Entity Creation ล้มเหลว Database Record ยังสามารถใช้เป็น Source of Truth สำหรับ Retry/Restore ได้

อย่าคิดว่า Runtime NetID เป็นส่วนของ Database Transaction Identity

68. Transaction ช่วย Persistent Vehicle อย่างไร

สมมติการสร้าง Vehicle Record ต้องมี:

vehicles
+
vehicle_settings

พร้อมกัน

ใช้ Transaction ให้สอง Records ถูก Commit เป็นชุด

จากนั้นค่อย:

CreateVehicleServerSetter

สร้าง Runtime Entity หลัง Database Operation สำเร็จ

69. Transaction ใช้กับ Logs ทุกครั้งไหม

ไม่จำเป็น

ถ้า Log เป็น Best-effort Record และไม่ควรทำให้ Business Operation ทั้งหมด Fail อาจไม่ควรรวม Log Query เข้า Transaction หลัก

ตัวอย่าง:

สร้าง Profile สำเร็จ
แต่
Audit Log Insert ล้ม

ต้องถามว่า Requirement จริงคือ:

ควร Rollback Profile ด้วยหรือไม่?

ถ้าไม่ ควรแยก Log ออกจาก Transaction

70. กฎเลือก Query เข้า Transaction

ถามทีละ Query:

ถ้า Query นี้ล้มเหลว
อีก Queries ที่สำเร็จก่อนหน้า
ควรถูกยกเลิกหรือไม่?

ถ้าคำตอบคือ:

ใช่

มีเหตุผลให้อยู่ Transaction เดียวกัน

ถ้า:

ไม่

อาจควรแยก Operation

ตาราง oxmysql Transaction ที่ควรรู้

เรื่องค่า/พฤติกรรม
FunctionMySQL.transaction
PromiseMySQL.transaction.await
ReturnBoolean
ทุก Query สำเร็จCommit
มี Query ล้มเหลวไม่ Commit ทั้งชุด
Isolation ConVarmysql_transaction_isolation_level
Isolation Range1–4
Default2 — Read Committed

ข้อมูลนี้ตรงกับ Transaction Documentation ปัจจุบันของ oxmysql.

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

พื้นฐาน:

local queries = {
    {
        query = [[
            INSERT INTO `table_a`
                (`value`)
            VALUES (?)
        ]],
        values = {
            valueA
        }
    },

    {
        query = [[
            INSERT INTO `table_b`
                (`value`)
            VALUES (?)
        ]],
        values = {
            valueB
        }
    }
}

local success =
    MySQL.transaction.await(queries)

if not success then
    print('Transaction failed')
    return
end

oxmysql รองรับ Specific Transaction Format และ Return Boolean.

FiveM Transaction failed แก้อย่างไร

ตรวจ:

SQL Syntax
↓
Parameters
↓
Constraints
↓
Column Types
↓
Foreign Keys
↓
Database Connection
↓
Specific Query ที่ Error

แล้วเปิด Database Debug เพื่อดู Query จริง

oxmysql ระบุว่าความเร็ว Query และ Debug Information สามารถดูผ่าน Debug UI/mysql_debug ได้.

FiveM Transaction Commit ครึ่งเดียวได้ไหม

ตาม Guarantee ของ MySQL.transaction ถ้ามีหนึ่ง Query ล้มเหลว Queries ใน Transaction ไม่ควรถูก Commit เป็นบางส่วน.

ถ้าเห็นข้อมูลเหมือนสำเร็จครึ่งเดียว ให้ตรวจว่า:

Queries ทั้งหมดอยู่ Transaction เดียวจริงหรือไม่
มี Query นอก Transaction หรือไม่
Runtime Cache ทำให้ดูเหมือน Database เปลี่ยนหรือไม่

FiveM Transaction ควรใช้กับ Character Creation ไหม

เหมาะหาก Character Creation ต้องสร้างหลาย Records และ Requirement กำหนดว่าต้องมีครบก่อน Character ใช้งานได้

เช่น:

Character
+
Settings
+
Profile Metadata

แต่ Schema จริงควรออกแบบตาม Framework ที่ใช้งาน

FiveM Transaction ควรใช้กับ Player Save ไหม

ใช้ได้เมื่อหลาย Updates ต้องเป็น State เดียวกันจริง

แต่ถ้า Data แต่ละกลุ่ม Save ได้อิสระ ไม่จำเป็นต้องรวมทุกอย่างไว้ Transaction ขนาดใหญ่

FiveM Transaction ทำให้ Server Lag ไหม

ไม่สามารถสรุปจากการใช้ Transaction เพียงอย่างเดียว

Performance ขึ้นกับ Queries ภายใน, Database Hardware, Settings, Version และ Workload และ oxmysql แนะนำดู Query Speed จริงผ่าน Debug Tools.

ถ้าพบ Hitch ให้ใช้ FiveM Profiler และ Database Debug ร่วมกัน

FiveM Transaction Isolation Level ควรตั้งอะไร

oxmysql ปัจจุบันใช้ Default:

2

หรือ:

Read Committed

หากไม่มีเหตุผลด้าน Concurrency ที่ชัดเจน ให้เริ่มจาก Default และอย่าเปลี่ยนเพราะคิดว่าเลข 4 ปลอดภัยกว่าทุกกรณี

FiveM Transaction กับ Prepare เลือกอะไร

ถ้าปัญหาคือ:

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

ใช้ Transaction

ถ้าปัญหาคือ:

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

จึงค่อยพิจารณา Prepare ซึ่ง oxmysql ระบุว่าออกแบบมาสำหรับ Frequently-called Queries.

FiveM Transaction ต้องใช้ await ไหม

ไม่จำเป็น เพราะ oxmysql รองรับทั้ง:

Promise/Await

และ:

Callback

สำหรับ Transaction.

เลือกตาม Code Style ของ Resource

FiveM Transaction ปลอดภัยจาก Client Cheat ไหม

Transaction ไม่ได้เป็น Security System

Network Event ยังต้อง Validate ฝั่ง Server โดย Cfx.re แนะนำให้ตรวจข้อมูลเช่น Player State, Position, Permissions และค่า Gameplay สำคัญผ่าน Server-side Methods แทนการเชื่อ Client.

ดังนั้น:

Secure Event
+
Server Validation
+
Database Transaction

ต้องทำงานร่วมกัน

FAQ FiveM MySQL Transaction

FiveM MySQL Transaction คืออะไร

คือการ Execute SQL หลาย Queries เป็นชุด โดย Commit เมื่อทุก Query สำเร็จ และหากหนึ่ง Queryล้มเหลวจะไม่ Commit Queries ใน Transaction.

oxmysql ใช้ Function อะไรสำหรับ Transaction

ใช้:

MySQL.transaction

หรือ Promise Style:

MySQL.transaction.await

MySQL.transaction คืนค่าอะไร

คืน Boolean ระบุผลของ Transaction.

Transaction มี Query ล้มหนึ่งตัวจะเกิดอะไร

oxmysql ระบุว่า Queries ใน Transaction จะไม่ถูก Commit.

Transaction เหมาะกับ Character Creation ไหม

เหมาะหาก Character Creation ต้องสร้างหลาย Persistent Records พร้อมกันและไม่ควรเหลือข้อมูลเพียงบางส่วน

Transaction ทำให้ Query เร็วขึ้นไหม

ไม่ใช่จุดประสงค์หลัก Performance ต้องวัดแยก และ oxmysql ระบุว่า Query Speeds แตกต่างตาม Hardware, Database Settings, Version และ Workload.

Transaction Isolation Level Default เท่าไร

oxmysql ระบุ Default ปัจจุบันเป็น:

2

หรือ Read Committed.

Isolation Level มีค่าอะไรบ้าง

oxmysql รองรับ 1–4: Repeatable Read, Read Committed, Read Uncommitted และ Serializable ตามลำดับ.

Transaction แทน Server Validation ได้ไหม

ไม่ได้ Cfx.re แนะนำให้ Network Events ตรวจข้อมูลสำคัญฝั่ง Server อยู่เสมอ.

Transaction แทน Parameterized Query ได้ไหม

ไม่ได้ Queries ภายใน Transaction ยังควรใช้ Parameter Values แยกจาก SQL Structure

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

MySQL.insert ใช้เพิ่ม Record และคืน Insert ID ส่วน Transaction ใช้หลาย Queries และคืน Boolean ของ Transaction.

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

MySQL.update คืนจำนวน Rows ที่ได้รับผล ส่วน Transaction คืน Success/Failure ของชุด Queries.

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

FiveM MySQL.transaction ของ oxmysql มีหน้าที่สำคัญคือทำให้ หลาย Database Operations เป็นชุดเดียวกัน โดยจะ Commit เมื่อทุก Query สำเร็จ และถ้ามี Query หนึ่งล้มเหลวจะไม่ Commitชุด Transaction นั้น.

ดังนั้น Transaction เหมาะกับระบบเช่น:

Character
+
Settings

หรือ

Persistent Record
+
Related Metadata

ที่ไม่ควรมีข้อมูลเพียงครึ่งเดียว

แต่ Transaction ไม่ได้แทน:

Server Validation
Parameterized Query
Index
Cache
Backup
Profiler

และไม่ได้มีหน้าที่ทำให้ Query เร็วขึ้นโดยอัตโนมัติ หาก Transaction ช้าให้เปิด mysql_debug/Debug UI แล้วตรวจ Query ภายใน เพราะ oxmysql ระบุว่า Query Performance ขึ้นกับ Hardware, Database Settings, Version และ Current Workload.

ด้าน Security Cfx.re แนะนำให้ Network Events ตรวจข้อมูลสำคัญจาก Server-side Methods และไม่เชื่อ Client Input โดยตรง ดังนั้น Flow ที่เหมาะคือ Client Request → Server Validate → Transaction → Commit → Update Runtime State.

สำหรับผู้อ่าน comsiam ให้จำสูตร “Validate → Transaction → Commit → Update Runtime” และ comsiam แนะนำให้ใช้ Transaction เฉพาะ Queries ที่จำเป็นต้องสำเร็จพร้อมกันจริงๆ ไม่ควรรวม Queries ที่ไม่เกี่ยวข้องเข้า Transaction ใหญ่เพียงชุดเดียว เพราะเป้าหมายของ Transaction คือรักษาความถูกต้องของข้อมูล ไม่ใช่รวมทุก SQL ของ Resource เข้าด้วยกัน

Comments

Popular posts from this blog

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

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

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