FiveM MySQL Upsert คืออะไร? วิธีใช้ INSERT ON DUPLICATE KEY UPDATE กับ oxmysql ให้เพิ่มหรืออัปเดตข้อมูลใน Query เดียว
FiveM MySQL Upsert คือรูปแบบ SQL ที่รวมแนวคิด INSERT + UPDATE ไว้ในคำสั่งเดียว โดยถ้ายังไม่มี Row ที่ชนกับ PRIMARY KEY หรือ UNIQUE KEY ระบบจะ Insert Record ใหม่ แต่ถ้ามี Duplicate Key อยู่แล้ว Database จะ Update Row เดิมแทนผ่านคำสั่ง INSERT ... ON DUPLICATE KEY UPDATE.
oxmysql แนะนำ Upsert โดยตรงสำหรับกรณีที่ Resource ไม่แน่ใจว่า Row นั้นมีอยู่แล้วหรือยัง และระบุว่าแนวทางนี้ดีกว่าการ Query เช็กก่อนว่ามี Row หรือไม่ แล้วจึงเลือกว่าจะ INSERT หรือ UPDATE.
จำง่ายๆ:
ไม่มีข้อมูล
→ INSERT
มีข้อมูลที่ชน UNIQUE/PRIMARY KEY
→ UPDATE
แทนรูปแบบเดิม:
SELECT
↓
มีไหม?
↓
YES → UPDATE
NO → INSERT
① Upsert คืออะไร
คำว่า Upsert มาจากแนวคิด:
UPDATE
+
INSERT
=
UPSERT
MariaDB รองรับรูปแบบ:
INSERT INTO table_name (...)
VALUES (...)
ON DUPLICATE KEY UPDATE
column = value;
เมื่อเกิด Duplicate ของ Primary Key หรือ Unique Key จะเปลี่ยนจากการ Insert เป็นการ Update Row ที่ตรงกับ Key นั้น.
② ตัวอย่าง Upsert พื้นฐาน
สมมติมี Table:
CREATE TABLE `player_settings` (
`identifier` VARCHAR(64) NOT NULL,
`theme` VARCHAR(30) NOT NULL,
PRIMARY KEY (`identifier`)
);
ต้องการบันทึก Theme:
INSERT INTO `player_settings`
(`identifier`, `theme`)
VALUES
(?, ?)
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`);
ถ้า Identifier ยังไม่มี:
INSERT Row ใหม่
ถ้ามีแล้ว:
UPDATE theme ของ Row เดิม
MariaDB ระบุว่า VALUES(column) ภายในส่วน ON DUPLICATE KEY UPDATE ใช้อ้างถึงค่าที่กำลังจะถูก Insert.
③ ทำไม Upsert มีประโยชน์กับ FiveM
FiveM Resource มีข้อมูลหลายประเภทที่มักใช้รูปแบบ:
ถ้าไม่มี
→ สร้าง
ถ้ามีแล้ว
→ อัปเดต
เช่น:
Player Settings
UI Preferences
Character Metadata
Resource Configuration ต่อผู้เล่น
Persistent State บางประเภท
Last-known Settings
Profile Options
Upsert ช่วยลด Logic แบบ Check-before-write ให้เหลือ SQL Statement เดียว
④ oxmysql แนะนำ Upsert หรือไม่
แนะนำ
เอกสารหลักของ oxmysql มีหัวข้อ Upserting โดยตรง และแสดงตัวอย่าง:
MySQL.prepare(
[[
INSERT INTO ox_inventory
(owner, name, data)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
data = VALUES(data)
]],
{
owner,
dbId,
inventory
}
)
พร้อมระบุว่าแนวทางนี้ควรใช้แทนการตรวจว่า Row มีอยู่ก่อนแล้วค่อยเลือก Insert หรือ Update.
⑤ วิธีเดิม SELECT → INSERT/UPDATE มีปัญหาอะไร
ตัวอย่าง:
local exists =
MySQL.scalar.await(
'SELECT 1 FROM `player_settings` WHERE `identifier` = ? LIMIT 1',
{
identifier
}
)
if exists then
MySQL.update.await(
'UPDATE `player_settings` SET `theme` = ? WHERE `identifier` = ?',
{
theme,
identifier
}
)
else
MySQL.insert.await(
'INSERT INTO `player_settings` (`identifier`, `theme`) VALUES (?, ?)',
{
identifier,
theme
}
)
end
Flow นี้ต้อง:
Query 1
→ SELECT
แล้ว
Query 2
→ INSERT หรือ UPDATE
Upsert สามารถลดเหลือ:
Query เดียว
ซึ่งเป็น Pattern ที่ oxmysql แนะนำ.
⑥ เขียน Upsert ด้วย oxmysql อย่างไร
ตัวอย่าง:
local function saveTheme(
identifier,
theme
)
return MySQL.prepare.await(
[[
INSERT INTO `player_settings`
(`identifier`, `theme`)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`)
]],
{
identifier,
theme
}
)
end
MySQL.prepare เหมาะกับ Query Structure เดิมที่ถูกเรียกบ่อย และรองรับ ? Value Placeholders.
⑦ ต้องใช้ MySQL.prepare เท่านั้นไหม
ไม่
Upsert เป็น SQL Syntax ของ Database
ส่วน:
MySQL.prepare
MySQL.query
MySQL.update
เป็น API ของ oxmysql
ดังนั้นแนวคิด Upsert ไม่ได้ผูกอยู่กับ Prepare เพียงตัวเดียว แต่ oxmysql ใช้ MySQL.prepare ในตัวอย่าง Upsert อย่างเป็นทางการ.
⑧ Upsert ต้องมี PRIMARY KEY หรือ UNIQUE KEY ไหม
ต้องมี Key ที่สามารถเกิด Duplicate Conflict ได้จึงจะเข้าส่วน:
ON DUPLICATE KEY UPDATE
MariaDB ระบุว่า Behavior นี้เกิดเมื่อ Insert พบ Duplicate บน PRIMARY KEY หรือ UNIQUE KEY.
ถ้า Column:
identifier
ไม่ได้ Unique และ Table อนุญาตข้อมูลซ้ำ Upsert อาจ Insert Row ใหม่แทนที่จะ Update Row ที่ Developerคาดหวัง
⑨ ตัวอย่าง UNIQUE KEY
CREATE TABLE `player_settings` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`identifier` VARCHAR(64) NOT NULL,
`theme` VARCHAR(30) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_player_settings_identifier`
(`identifier`)
);
ตอนนี้:
identifier
ไม่สามารถซ้ำได้
ดังนั้น Upsert ที่ Insert Identifier เดิมจะเข้าส่วน Update
⑩ ถ้าไม่มี UNIQUE KEY จะเกิดอะไร
สมมติ:
CREATE TABLE `player_settings` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`identifier` VARCHAR(64) NOT NULL,
`theme` VARCHAR(30) NOT NULL,
PRIMARY KEY (`id`)
);
แล้ว Resource เรียก:
INSERT INTO `player_settings`
(`identifier`, `theme`)
VALUES
('license:abc', 'dark')
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`);
ถ้า identifier ไม่มี Unique Constraint และ id เป็น Auto Increment ใหม่ทุกครั้ง Database อาจ Insert Record ใหม่เพราะไม่มี Duplicate Key ที่เกี่ยวข้อง
ผลอาจกลายเป็น:
license:abc | dark
license:abc | light
license:abc | blue
ดังนั้น Schema สำคัญมาก
⑪ Upsert ไม่ได้หา Duplicate จากทุก Column
ON DUPLICATE KEY UPDATE ไม่ได้หมายถึง:
ถ้ามีข้อมูลเหมือนกันตรง Column ไหนก็ได้ให้ Update
มันพิจารณา Conflict กับ Key ที่มี Constraint เช่น Primary/Unique.
ดังนั้นต้องกำหนด Business Key ให้ชัด
⑫ Business Key คืออะไร
คือข้อมูลที่ Application ใช้กำหนดว่า Record ใดควรมีเพียงหนึ่งชุด
ตัวอย่าง Player Settings:
identifier
อาจเป็น Business Key
Vehicle Setting:
vehicle_id
+
setting_name
อาจต้องใช้ Composite Unique Key
ตัวอย่าง:
UNIQUE KEY `uq_vehicle_setting`
(`vehicle_id`, `setting_name`)
จากนั้น Upsert สามารถอ้าง Conflict ของคู่ค่านี้ได้
⑬ Composite UNIQUE KEY ใช้ Upsert ได้ไหม
ได้
ตัวอย่าง:
CREATE TABLE `character_settings` (
`character_id` INT UNSIGNED NOT NULL,
`setting_name` VARCHAR(50) NOT NULL,
`setting_value` TEXT NULL,
UNIQUE KEY `uq_character_setting`
(`character_id`, `setting_name`)
);
Query:
INSERT INTO `character_settings`
(
`character_id`,
`setting_name`,
`setting_value`
)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
`setting_value` =
VALUES(`setting_value`);
ถ้า:
character_id + setting_name
ซ้ำ Database จะ Update
⑭ VALUES(column) คืออะไร
ใน:
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`)
VALUES(theme) หมายถึงค่าของ theme ที่อยู่ในส่วน INSERT
MariaDB ระบุว่า VALUES(col_name) ใช้ใน ON DUPLICATE KEY UPDATE เพื่ออ้างค่าที่จะถูก Insert หากไม่เกิด Duplicate Conflict.
⑮ ตัวอย่าง VALUES()
Query:
INSERT INTO `player_settings`
(`identifier`, `theme`)
VALUES
(?, ?)
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`);
Parameters:
{
'license:abc',
'dark'
}
ถ้ามี Record เดิม:
identifier = license:abc
theme = light
หลัง Upsert:
identifier = license:abc
theme = dark
เพราะ VALUES(theme) คือ dark
⑯ Upsert หลาย Columns ได้ไหม
ได้
ตัวอย่าง:
INSERT INTO `player_settings`
(
`identifier`,
`theme`,
`language`,
`updated_at`
)
VALUES
(?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`),
`language` = VALUES(`language`),
`updated_at` = NOW();
เมื่อ Duplicate จะ Update Columns ที่ระบุในส่วน Update
ไม่จำเป็นต้อง Update ทุก Column ของ Row
⑰ Column ไหนไม่ใส่ใน UPDATE จะเป็นอย่างไร
สมมติ:
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`)
แต่ไม่ได้ใส่:
language
created_at
เมื่อ Duplicate เกิด Columns เหล่านั้นจะไม่ถูกแก้โดยส่วน Update นี้
จึงสามารถออกแบบให้:
created_at
คงค่าเดิม
แต่:
updated_at
เปลี่ยนทุกครั้ง
⑱ created_at กับ updated_at ควรใช้ยังไง
ตัวอย่าง:
INSERT INTO `player_settings`
(
`identifier`,
`theme`,
`created_at`,
`updated_at`
)
VALUES
(?, ?, NOW(), NOW())
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`),
`updated_at` = NOW();
ผล:
Insert ใหม่
created_at = เวลาสร้าง
updated_at = เวลาสร้าง
Update เดิม
created_at = ไม่เปลี่ยน
updated_at = เวลาปัจจุบัน
เป็น Pattern ที่ใช้ได้กับข้อมูล Persistent ทั่วไป
⑲ Upsert กับ MySQL.insert ต่างกันอย่างไร
MySQL.insert มีหน้าที่หลักคือ Insert Record และคืน Insert ID หากใช้ได้.
ตัวอย่าง:
local id =
MySQL.insert.await(
[[
INSERT INTO `profiles`
(`identifier`)
VALUES (?)
]],
{
identifier
}
)
Upsert มี Requirement เพิ่มคือ:
ถ้ามี Duplicate Key
→ Update
ดังนั้นจุดประสงค์ต่างกัน
⑳ Upsert กับ MySQL.update ต่างกันอย่างไร
MySQL.update เหมาะเมื่อรู้ว่า Record มีอยู่แล้วและต้องการ Update
oxmysql ระบุว่า MySQL.update คืนจำนวน Rows ที่ได้รับผล.
ตัวอย่าง:
local affected =
MySQL.update.await(
[[
UPDATE `player_settings`
SET `theme` = ?
WHERE `identifier` = ?
]],
{
theme,
identifier
}
)
Upsert เหมาะเมื่อ:
ไม่แน่ใจว่า Record มีหรือไม่
㉑ Upsert กับ Transaction ต่างกันอย่างไร
Upsert
หนึ่ง SQL Statement
→ Insert หรือ Update
Transaction
หลาย Queries
→ Commit เมื่อทั้งหมดสำเร็จ
oxmysql ระบุว่า Transaction จะ Commit ก็ต่อเมื่อ Queries ทั้งหมดสำเร็จ และถ้าหนึ่ง Query ล้มเหลวจะไม่ Commit ทั้งชุด.
จึงเป็นเครื่องมือคนละระดับ
㉒ Upsert ใช้ใน Transaction ได้ไหม
ได้ หาก Business Operation ต้องมีหลาย Database Operations ที่ต้องสำเร็จพร้อมกัน
ตัวอย่างเชิงแนวคิด:
Transaction
├── Upsert profile
├── Upsert settings
└── Insert related record
ถ้าทุก Query ต้อง Commit พร้อมกัน Transaction ยังมีประโยชน์
Upsert ไม่ได้แทน Atomicity ระหว่างหลาย Tables
㉓ Upsert กับ REPLACE INTO ต่างกันอย่างไร
นี่เป็นข้อแตกต่างสำคัญ
oxmysql ระบุว่า Upsert แบบ ON DUPLICATE KEY UPDATE ต่างจาก REPLACE INTO เพราะ Row เดิม ไม่ได้ถูกลบแล้ว Insert ใหม่.
MariaDB ระบุว่า REPLACE ทำงานคล้าย INSERT แต่ถ้ามี Duplicate Key จะ Delete Row ที่ชนก่อนแล้วจึง Insert Row ใหม่.
จำ:
ON DUPLICATE KEY UPDATE
→ UPDATE Row เดิม
REPLACE
→ DELETE Row เดิม
→ INSERT ใหม่
㉔ ทำไม REPLACE อาจอันตรายกว่าในบาง Schema
เพราะ Delete + Insert อาจมีผลต่างจาก Update เช่น:
Auto Increment
Foreign Key
Delete Trigger
Insert Trigger
Related Data
ดังนั้น oxmysql จึงแนะนำ Upsert และระบุข้อดีชัดเจนว่า Row ไม่ถูกลบและ Insert ใหม่เหมือน REPLACE INTO.
㉕ INSERT IGNORE ต่างจาก Upsert อย่างไร
MariaDB รองรับ INSERT IGNORE ซึ่งเมื่อพบ Duplicate/Error บางประเภทจะเปลี่ยน Error เป็น Warning และไม่ทำงานแบบ Update ค่าเดิมเหมือน ON DUPLICATE KEY UPDATE.
จำ:
INSERT IGNORE
→ เจอ Duplicate
→ ไม่ Update ค่าเดิมแบบ Upsert
ON DUPLICATE KEY UPDATE
→ เจอ Duplicate
→ Update ตามที่กำหนด
㉖ Upsert เหมาะกับ Player Settings มาก
ตัวอย่าง Table:
CREATE TABLE `player_settings` (
`identifier` VARCHAR(64) NOT NULL,
`theme` VARCHAR(30) NOT NULL DEFAULT 'default',
`language` VARCHAR(10) NOT NULL DEFAULT 'th',
PRIMARY KEY (`identifier`)
);
บันทึก:
local function saveSettings(
identifier,
theme,
language
)
MySQL.prepare.await(
[[
INSERT INTO `player_settings`
(
`identifier`,
`theme`,
`language`
)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`),
`language` = VALUES(`language`)
]],
{
identifier,
theme,
language
}
)
end
ไม่ต้อง SELECT ก่อนว่าผู้เล่นมี Settings Row แล้วหรือยัง
㉗ Upsert เหมาะกับ Character Metadata
ตัวอย่าง:
CREATE TABLE `character_meta` (
`character_id` INT UNSIGNED NOT NULL,
`meta_key` VARCHAR(50) NOT NULL,
`meta_value` TEXT NULL,
UNIQUE KEY `uq_character_meta`
(`character_id`, `meta_key`)
);
Upsert:
MySQL.prepare.await(
[[
INSERT INTO `character_meta`
(
`character_id`,
`meta_key`,
`meta_value`
)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
`meta_value` =
VALUES(`meta_value`)
]],
{
characterId,
key,
value
}
)
เหมาะเมื่อ Metadata หนึ่ง Key ควรมีเพียงหนึ่ง Row ต่อ Character
㉘ Upsert เหมาะกับ Last Seen หรือไม่
ได้ในบาง Architecture
ตัวอย่าง:
INSERT INTO `player_activity`
(
`identifier`,
`last_seen`
)
VALUES
(?, NOW())
ON DUPLICATE KEY UPDATE
`last_seen` = NOW();
ถ้ายังไม่มี Player:
สร้าง Row
ถ้ามี:
อัปเดต last_seen
㉙ Upsert ใช้แทน UPDATE ทุกอย่างไหม
ไม่
ถ้าระบบรู้แน่นอนว่า Record ต้องมีอยู่และถ้าไม่มีถือเป็น Error:
Profile ID ต้องมี
แต่หาไม่เจอ
→ ควรถือว่าผิดปกติ
UPDATE ปกติอาจเหมาะกว่า Upsert เพราะการสร้าง Record ใหม่อัตโนมัติอาจซ่อน Bug
㉚ ตัวอย่างที่ไม่ควรใช้ Upsert
สมมติ Admin ต้องแก้ Character:
character_id = 500
ถ้า Character 500 ไม่มีจริง สิ่งที่ต้องการอาจเป็น:
ERROR
ไม่ใช่:
สร้าง Character 500 ใหม่
กรณีนี้ใช้ UPDATE ... WHERE id = ? และตรวจ affectedRows ชัดกว่า
㉛ ใช้ Upsert เมื่อ Business Rule อนุญาตให้สร้างได้
ถามว่า:
ถ้า Record ยังไม่มี ระบบควรสร้างมันอัตโนมัติจริงหรือไม่?
ถ้าคำตอบ:
YES
Upsert เป็น Candidate
ถ้า:
NO
ใช้ Update/Validation ตามปกติ
㉜ Upsert ช่วยลด Race Condition แบบ Check-before-write หรือไม่
Upsert ช่วยหลีกเลี่ยง Application Pattern:
SELECT ว่ามีไหม
↓
ค่อย INSERT/UPDATE
โดยให้ Database ตัดสินจาก Unique/Primary Key ใน SQL Statement เดียว ซึ่งเป็นเหตุผลหนึ่งที่ oxmysql แนะนำ Upsert เหนือการตรวจ Record ก่อน.
แต่ Resource ยังต้องออกแบบ Constraints และ Concurrency ของ Business Logic ให้ถูกต้อง
㉝ Unique Constraint สำคัญกับ Concurrency อย่างไร
ถ้าสอง Requests พยายามสร้าง:
identifier = license:abc
ใกล้กัน
Unique Key เป็น Rule ที่ Database ใช้ป้องกันไม่ให้มีค่าซ้ำ
Upsert จึงทำงานบน Constraint จริง แทนการอาศัยว่า Application เคย SELECT แล้วพบว่าไม่มี
㉞ อย่าใช้ SELECT EXISTS ก่อน Upsert
หากสุดท้าย Query ต้องทำ:
Insert if missing
Update if exists
oxmysql ระบุชัดว่า Upsert ควรถูกใช้แทนการตรวจว่ามี Row แล้วค่อย Insert หรือ Update.
ดังนั้น Pattern นี้:
SELECT EXISTS
↓
Upsert
มักเป็น Query ที่ซ้ำซ้อนหากไม่มี Logic อื่นต้องใช้ผลจาก SELECT
㉟ Upsert ช่วยลด Query Count
เดิม:
SELECT
+
INSERT/UPDATE
=
อย่างน้อย 2 Database Operations
Upsert:
INSERT ... ON DUPLICATE KEY UPDATE
=
1 SQL Statement
สำหรับ Hot Path ที่ถูกเรียกบ่อย การลด Round Trips ที่ไม่จำเป็นเป็นเรื่องที่ควรพิจารณา
แต่ต้อง Benchmark Workload จริง
㊱ Upsert ทำให้ Server เร็วขึ้นแน่นอนไหม
ไม่สามารถรับประกัน
oxmysql ระบุว่า Query Performance ขึ้นกับ:
Hardware
Database Settings
Database Version
Current Workload
และแนะนำดู Real Query Speeds ผ่าน Debug UI หรือ mysql_debug.
Upsert ลด Query Flow ได้ แต่ SQL และ Index ยังต้องเหมาะสม
㊲ MySQL.prepare ช่วย Upsert ได้อย่างไร
oxmysql ระบุว่า MySQL.prepare เหมาะกับ Frequently-called Queries และรองรับ Query เดียวกับ Parameter Sets ที่เปลี่ยนไป.
ดังนั้น Upsert ที่เรียกบ่อย เช่น:
Save Setting
Save Metadata
Save Preference
เป็น Candidate ที่เหมาะกับ Prepare
㊳ อย่าใช้ Prepare แทน UNIQUE KEY
Prepare ไม่ได้ทำให้ Column Unique
ถ้า Table ไม่มี:
PRIMARY KEY
หรือ
UNIQUE KEY
ที่สัมพันธ์กับข้อมูล Upsert ก็ไม่มี Duplicate Key Conflict ที่ต้องการให้ Update
ดังนั้น:
Prepare
≠ Constraint
㊴ Index กับ Upsert เกี่ยวกันไหม
เกี่ยว
UNIQUE KEY เป็นทั้ง Constraint และ Index Structure ของ Database
แต่ Regular Index อย่าง:
INDEX (`identifier`)
ไม่ได้ทำให้ค่าห้ามซ้ำ
ถ้าต้องการ Duplicate Detection ต้องใช้ Constraint ที่เหมาะสม เช่น:
UNIQUE KEY (`identifier`)
㊵ Regular Index ไม่เท่ากับ UNIQUE Index
จำ:
INDEX(identifier)
→ ค่าซ้ำได้
UNIQUE INDEX(identifier)
→ ค่าซ้ำไม่ได้
Upsert ต้องเกิด Conflict กับ Unique/Primary Key จึงเข้าส่วน Update.
㊶ ตัวอย่าง Schema ที่ถูก
CREATE TABLE `resource_preferences` (
`identifier` VARCHAR(64) NOT NULL,
`preference_key` VARCHAR(50) NOT NULL,
`preference_value` TEXT NULL,
UNIQUE KEY `uq_resource_preference`
(
`identifier`,
`preference_key`
)
);
Upsert:
INSERT INTO `resource_preferences`
(
`identifier`,
`preference_key`,
`preference_value`
)
VALUES
(?, ?, ?)
ON DUPLICATE KEY UPDATE
`preference_value` =
VALUES(`preference_value`);
㊷ UNIQUE หลายตัวใน Table ต้องระวัง
MariaDB Documentation เตือนว่า INSERT ... ON DUPLICATE KEY UPDATE ไม่แนะนำสำหรับ Table ที่มี Unique Index มากกว่าหนึ่งชุดที่สามารถ Match ได้ เพราะหากมี Conflict หลาย Unique Index ระบบจะ Update ตาม Key ที่ Match ก่อนเท่านั้น.
ดังนั้น Schema ที่ซับซ้อนควรทดสอบ Conflict Cases ให้ครบ
㊸ ตัวอย่างปัญหา Unique หลายชุด
สมมติ:
UNIQUE(identifier)
UNIQUE(email)
แล้ว Insert Row ใหม่ที่:
identifier
ชนกับ Row A
แต่:
email
ชนกับ Row B
นี่เป็น Conflict ที่ซับซ้อนและไม่ควรพึ่ง Upsert แบบไม่วิเคราะห์
ควรออกแบบ Business Key ให้ชัด
㊹ Upsert ควร Validate Input ไหม
ต้อง
Database Upsert ไม่ได้ทำให้ Client Request ถูกต้องหรือมีสิทธิ์โดยอัตโนมัติ
Flow ที่เหมาะ:
Client Request
↓
Server ตรวจ Input
↓
Server ตรวจ Player
↓
Server ตรวจสิทธิ์
↓
Upsert
อย่าใช้ Upsert เป็น Permission System
㊺ อย่าให้ Client ส่ง Identifier แล้วเชื่อทันที
ถ้า Server หา Identifier จาก source ได้เอง ควรทำฝั่ง Server
ตัวอย่าง:
local identifier =
GetPlayerIdentifierByType(
source,
'license'
)
แล้วใช้ Identifier ที่ Server Resolve เองในการ Query
ไม่จำเป็นต้องให้ Client บอกว่า:
ฉันคือ license:xxx
㊻ ตัวอย่างบันทึก Setting ฝั่ง Server
RegisterNetEvent(
'settings:saveTheme',
function(theme)
local src = source
if type(theme) ~= 'string' then
return
end
local allowedThemes = {
dark = true,
light = true
}
if not allowedThemes[theme] then
return
end
local identifier =
GetPlayerIdentifierByType(
src,
'license'
)
if not identifier then
return
end
MySQL.prepare.await(
[[
INSERT INTO `player_settings`
(`identifier`, `theme`)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`)
]],
{
identifier,
theme
}
)
end
)
ตรงนี้ Client เลือกได้เฉพาะ Theme ที่ Server Allowlist ไว้
㊼ Parameterized Query ยังสำคัญไหม
สำคัญ
ใช้:
VALUES (?, ?)
และ Parameters:
{
identifier,
theme
}
อย่าต่อ Input เข้า SQL String โดยตรง
oxmysql prepare รองรับ ? เป็น Value Placeholder และไม่รองรับ Named Placeholder ใน API นี้.
㊽ Upsert JSON Data ได้ไหม
ได้ถ้า Column/Schema รองรับและ Resource ต้องการเก็บ Structured Data
ตัวอย่าง:
local encoded =
json.encode(settings)
MySQL.prepare.await(
[[
INSERT INTO `player_data`
(`identifier`, `data`)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE
`data` = VALUES(`data`)
]],
{
identifier,
encoded
}
)
แต่ถ้า Resource ต้อง Search/Filter ข้อมูลย่อยภายใน Data บ่อย อาจต้องพิจารณา Schema ที่แยก Columns มากกว่าเก็บทุกอย่างเป็น Blob เดียว
㊾ Upsert Data ขนาดใหญ่ทุก Frame ดีไหม
ไม่
แม้ใช้ Upsert Query เดียวก็ไม่ควร Write Database ทุก Frame
ตัวอย่างที่ควรหลีกเลี่ยง:
CreateThread(function()
while true do
savePlayerData()
Wait(0)
end
end)
Database เป็น Persistent Layer ไม่ใช่ Runtime State Storage
㊿ ใช้ Dirty Flag กับ Upsert ได้ไหม
ได้ และเหมาะมาก
ตัวอย่าง:
PlayerState[id] = {
settings = settings,
dirty = false
}
เมื่อข้อมูลเปลี่ยน:
PlayerState[id].dirty = true
ตอน Save:
dirty = false
→ ไม่ Query
dirty = true
→ Upsert
ช่วยลด Write ที่ไม่จำเป็น
51. Upsert + Cache เป็น Pattern ที่ดี
ตัวอย่าง Architecture:
Player Login
↓
Load Database
↓
Cache ใน Memory
↓
Gameplay ใช้ Cache
↓
State เปลี่ยน
↓
Dirty = true
↓
Autosave / Important Event
↓
Upsert Database
ดีกว่าการ Query Database ทุกครั้งที่ Resource ต้องอ่าน Setting
52. Upsert + Autosave ต้องระวังอะไร
ถ้า Player จำนวนมาก Save พร้อมกัน:
200 Players
↓
200 Upserts
ในช่วงเวลาเดียว
Database Workload สามารถ Spike ได้
ควรพิจารณา:
Dirty Flag
Staggered Save
Save เฉพาะข้อมูลเปลี่ยน
และวัดด้วย oxmysql Debug Tools
53. เปิด mysql_debug ตรวจ Upsert ยังไง
oxmysql รองรับ:
set mysql_debug true
เพื่อ Print Queries ใน Server Console และยังสามารถ Debug เฉพาะ Resources ได้.
ใช้ตรวจว่า:
Upsert ถูกเรียกบ่อยแค่ไหน
Query ใช้เวลากี่ ms
Resource ไหนเรียก
54. Slow Upsert ต้องตรวจอะไร
ตรวจ:
Table Size
Unique Index
Columns ที่ Update
Query Frequency
Write Volume
Database CPU
Disk
Concurrent Writes
Server Hitch
และ Benchmark ด้วย Workload จริง เพราะ oxmysql ระบุว่า Query Speed เปลี่ยนตาม Hardware, Database Settings, Version และ Workload.
55. Upsert กับ Slow Query Warning
Upsert ก็สามารถกลายเป็น Slow Query ได้เหมือน SQL อื่น
อย่าคิดว่า:
Query เดียว
=
เร็วเสมอ
ถ้า Table ใหญ่, Write หนัก หรือ Constraints/Indexes ซับซ้อน ก็ต้องวัด Performance จริง
56. Upsert หลาย Columns ทุกครั้งอาจไม่จำเป็น
ถ้าผู้เล่นเปลี่ยนแค่:
theme
แต่ Query Update:
theme
language
volume
display_mode
notification
...
ทุกครั้ง อาจเขียนข้อมูลมากเกิน Requirement
ออกแบบ Save Strategy ให้เหมาะกับ Resource จริง
57. Upsert แยก Settings เป็น Key/Value ดีไหม
ขึ้นกับระบบ
แบบ:
identifier
setting_name
setting_value
มีความยืดหยุ่น
แต่ Query หลาย Settings อาจกลายเป็นหลาย Rows
อีกแบบ:
identifier
theme
language
volume
มี Structure ชัดเจน
ไม่มีแบบใดดีที่สุดสำหรับทุก Resource
58. Upsert Batch Data ได้ไหม
MariaDB รองรับ Multi-row Insert และ ON DUPLICATE KEY UPDATE ใน Syntax ของ INSERT.
ตัวอย่างเชิง SQL:
INSERT INTO `player_settings`
(`identifier`, `theme`)
VALUES
(?, ?),
(?, ?)
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`);
แต่ Parameter จำนวนมากและ Batch Size ต้องทดสอบกับ Database Workload จริง
59. Batch Upsert ดีกว่า Upsert ทีละ Row เสมอไหม
ไม่ควรสรุปแบบนั้น
ขึ้นกับ:
จำนวน Rows
Query Size
Database
Workload
Frequency
Failure Handling
ถ้ามีเพียง 2–3 Records บางครั้งความเรียบง่ายสำคัญกว่า Micro-optimization
60. Upsert กับ Transaction เมื่อ Batch สำคัญ
ถ้ามีหลาย Upserts ที่ต้อง:
สำเร็จทั้งหมด
หรือ
ไม่สำเร็จเลย
Transaction ยังเหมาะสม
oxmysql Transaction ถูกออกแบบเพื่อ Commit หลาย Queries เฉพาะเมื่อทุก Query สำเร็จ.
61. Upsert Result ควรตรวจอย่างไร
ขึ้นกับ oxmysql API ที่ใช้
ถ้าต้องการ:
Insert ID
MySQL.insert มี Return Contract สำหรับ Insert ID.
ถ้าต้องการ:
affectedRows
MySQL.update มี Return Contract สำหรับจำนวน Rows ที่ได้รับผล.
ส่วน prepare มี Result Behavior ตาม SQL ที่ Execute จึงควรทดสอบ Return Value ของ Upsert ที่ Resource ใช้จริง
62. อย่าผูก Business Logic กับ affectedRows แบบไม่ทดสอบ
Database อาจรายงานผลของ Insert และ Duplicate Update ต่างกันตาม Statement/Client Behavior
ดังนั้นหาก Resource ต้องแยกว่า:
สร้างใหม่
หรือ
อัปเดต
แบบมีผลต่อ Gameplay สำคัญ ควรออกแบบ Flow ให้ชัดและทดสอบกับ Database Version ที่ใช้อยู่จริง
อย่าเดาจากตัวเลขเพียงอย่างเดียว
63. Upsert ใช้กับ Auto Increment ได้ไหม
ได้หาก Table มี Auto Increment Primary Key และมี Unique Key อื่นที่ใช้ตรวจ Duplicate
ตัวอย่าง:
CREATE TABLE `profiles` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`identifier` VARCHAR(64) NOT NULL,
`display_name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_profiles_identifier`
(`identifier`)
);
Conflict สามารถเกิดจาก:
identifier
แม้ id จะ Auto Increment
64. อย่าใช้ Runtime Server ID เป็น Unique Persistent Key
FiveM source หรือ Server ID เปลี่ยนได้ตาม Connection Session
Persistent Table ควรใช้ Application/Persistent Identifier ที่เหมาะสม
เช่น:
character_id
profile_id
identifier
ตามระบบที่ออกแบบ
ไม่ควรใช้:
source = 27
เป็น Identity ถาวร
65. Upsert ใช้กับ NetID ได้ไหม
ไม่ควรใช้ NetID เป็น Persistent Identity ของ Entity
NetID เป็น Runtime Network Reference
ถ้าต้องเก็บ Persistent Vehicle Setting ควรอิง:
vehicle_id
หรือ Internal Persistent ID
จาก Database
66. Upsert กับ State Bag ต่างกันอย่างไร
Upsert
= Persistent Database Write
State Bag
= Runtime Network State
ตัวอย่าง:
Player เปลี่ยน Setting
↓
Server Validate
↓
Update Cache
↓
Update State Bag ถ้าต้อง Sync
↓
Upsert Database ตาม Save Strategy
ไม่จำเป็นต้อง Upsert ทุกครั้งที่ Network State เปลี่ยนเล็กน้อย
67. Upsert กับ Resource Restart
ข้อมูลที่ Upsert และ Commit แล้วจะอยู่ใน Database
แต่:
Lua Cache
Runtime Table
State Bag
อาจถูกสร้างใหม่เมื่อ Resource Restart
Resource จึงต้อง Reload Persistent State ตาม Architecture
68. Upsert Error Duplicate ยังเกิดได้ไหม
ถ้า Duplicate Conflict ไม่สามารถถูกจัดการตาม Statement/Schema ที่ออกแบบ หรือมี Constraint อื่นที่ไม่ตรงกับ Upsert Logic ก็ยังสามารถเกิด Database Error ได้
MariaDB ระบุว่า Duplicate Value ปกติจะทำให้ INSERT Error หากชน Unique Index เว้นแต่ใช้วิธีจัดการอย่าง IGNORE, ON DUPLICATE KEY UPDATE หรือ REPLACE.
ดังนั้นต้องอ่าน Error จริง ไม่ควรคิดว่า Upsert ลบ Duplicate Errors ทุกประเภท
69. Resource Update แล้ว Upsert พังควรตรวจอะไร
ตรวจ:
Schema เปลี่ยนหรือไม่
Unique Key ยังอยู่หรือไม่
Column ถูก Rename หรือไม่
Reserved Keyword หรือไม่
Database Version
Resource SQL Migration
oxmysql เองระบุว่า Resources รุ่นเก่าบางตัวอาจมี Compatibility Differences กับ Database Versions โดยเฉพาะ MySQL 8.
70. Upsert กับ MariaDB เหมาะไหม
MariaDB รองรับ INSERT ... ON DUPLICATE KEY UPDATE โดยตรง และ oxmysql Documentation แนะนำ MariaDB สำหรับ Compatibility กับ FiveM Resources.
แต่ Server ที่ใช้งาน Production อยู่แล้วไม่ควรย้าย Database Engine เพียงเพื่อใช้ Upsert เพราะต้องตรวจ Compatibility ของระบบทั้งหมดก่อน
Checklist FiveM MySQL Upsert
ก่อนใช้ตรวจว่า:
ต้องการ Insert หากไม่มีจริงหรือไม่
ต้องการ Update หากมีจริงหรือไม่
มี Primary/Unique Key ที่ถูกต้อง
Business Key ถูกออกแบบชัดเจน
ไม่มี Duplicate Records เดิมที่ผิด Schema
ตรวจ Existing Indexes
ไม่ใช้ Regular Index แทน Unique Constraint
ใช้
ON DUPLICATE KEY UPDATEใช้
VALUES(column)ตาม Use CaseUpdate เฉพาะ Columns ที่จำเป็น
ไม่เปลี่ยน
created_atโดยไม่ตั้งใจUpdate
updated_atเมื่อเหมาะสมใช้ Parameterized Query
ไม่ต่อ Client Input เข้า SQL
Validate Network Event
Server Resolve Identifier เมื่อทำได้
ตรวจ Permission
ตรวจ Record Ownership
ไม่ Upsert ทุก Frame
ใช้ Cache เมื่อเหมาะสม
ใช้ Dirty Flag
ระวัง Autosave Burst
Benchmark Query Frequency
เปิด
mysql_debugเมื่อต้องวิเคราะห์ตรวจ Slow Query
ไม่ใช้ Prepare แทน Unique Constraint
ไม่ใช้ Upsert แทน Transaction
ไม่ใช้ Upsert แทน Cache
ระวัง Table ที่มีหลาย Unique Keys
ทดสอบ Duplicate Case
ทดสอบ Insert Case
ทดสอบ Update Case
ทดสอบ Invalid Input
Backup ก่อนแก้ Schema
Test Schema Change บน Development Database
ตาราง Upsert เทียบกับคำสั่งอื่น
| วิธี | เมื่อไม่มี Row | เมื่อชน Duplicate |
|---|---|---|
INSERT | Insert | Error ตาม Constraint |
INSERT IGNORE | Insert | Ignore/Warning ตามกรณี |
INSERT ... ON DUPLICATE KEY UPDATE | Insert | Update Row เดิม |
REPLACE | Insert | Delete Row เดิมแล้ว Insert ใหม่ |
UPDATE | ไม่สร้าง Row ใหม่ | Update เฉพาะ Row ที่ Match |
MariaDB รองรับกลไกเหล่านี้แยกกัน และ oxmysql แนะนำ ON DUPLICATE KEY UPDATE เหนือ Check-then-insert/update และเหนือ REPLACE INTO สำหรับ Upsert Use Case.
FiveM Upsert ใช้ยังไง
ตัวอย่างที่ควรจำ:
MySQL.prepare.await(
[[
INSERT INTO `player_settings`
(`identifier`, `theme`)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE
`theme` = VALUES(`theme`)
]],
{
identifier,
theme
}
)
เป็น Pattern เดียวกับแนวทาง Upsert ที่ oxmysql Documentation แนะนำ.
FiveM INSERT ON DUPLICATE KEY UPDATE คืออะไร
คือ SQL ที่:
Insert ถ้า Key ยังไม่มี
Update ถ้า Primary/Unique Key ซ้ำ
MariaDB รองรับ Syntax นี้โดยตรง.
FiveM Upsert ต้องมี Unique Key ไหม
ต้องมี Key Conflict ที่ Database สามารถตรวจได้ เช่น Primary Key หรือ Unique Key จึงจะเข้าส่วน Update.
หาก Column ที่คิดว่าเป็น Identity ไม่มี Unique Constraint ระบบอาจสร้าง Rows ซ้ำ
FiveM Upsert ต่างจาก REPLACE ยังไง
Upsert:
Duplicate
→ UPDATE Row เดิม
REPLACE:
Duplicate
→ DELETE Row เดิม
→ INSERT ใหม่
oxmysql ระบุความแตกต่างนี้โดยตรง และแนะนำ Upsert มากกว่าการใช้ REPLACE INTO สำหรับ Use Case ดังกล่าว.
FiveM Upsert ดีกว่า SELECT ก่อนหรือไม่
ถ้าเป้าหมายคือเพียง:
มี → Update
ไม่มี → Insert
oxmysql แนะนำ Upsert แทนการตรวจว่ามี Row ก่อนแล้วค่อย Insert/Update.
แต่ถ้า SELECT นั้นจำเป็นสำหรับ Business Validation อื่น ก็ยังอาจต้อง Query เพิ่ม
FiveM Upsert ทำให้ Query น้อยลงไหม
โดยทั่วไป Pattern:
SELECT
+
INSERT/UPDATE
สามารถเปลี่ยนเป็น SQL Upsert Statement เดียวได้
แต่ Performance จริงต้องวัด เพราะ Query Speed ยังขึ้นกับ Database Environment และ Workload.
FiveM Upsert ใช้กับ MySQL.prepare ได้ไหม
ได้ และ oxmysql ใช้ MySQL.prepare ในตัวอย่าง Upsert อย่างเป็นทางการ.
Prepare เหมาะกับ Query ที่ถูกเรียกบ่อยและรองรับ ? Value Placeholders.
FiveM Upsert ใช้กับ Player Settings ดีไหม
เหมาะมากเมื่อ Requirement คือ:
Player ใหม่
→ สร้าง Settings
Player เดิม
→ Update Settings
และ Table มี Unique Key ที่บอกว่า Settings Row ใดเป็นของ Player ใด
FiveM Upsert ทำให้ Duplicate Data หายไหม
ไม่จำเป็น
Upsert ป้องกัน Duplicate ตาม Keys ที่ Database กำหนดเท่านั้น
ถ้า Database มี Rows ซ้ำอยู่แล้วและ Column ไม่มี Unique Constraint การเพิ่ม Upsert ใน Resource ไม่ได้ลบข้อมูลซ้ำเก่าให้อัตโนมัติ
ต้อง Cleanup Data และแก้ Schemaแยก
FiveM Upsert ช้าแก้อย่างไร
ตรวจ:
Query Count
Unique Index
Table Size
Columns Updated
Database Workload
Concurrent Writes
Server Hitch
แล้วใช้ mysql_debug/Debug UI เพื่อวัด Query จริง เพราะ oxmysql ระบุว่า Performance ขึ้นกับ Hardware, Settings, Database Version และ Current Workload.
FAQ FiveM MySQL Upsert
FiveM Upsert คืออะไร
คือการ Insert Record ถ้ายังไม่มี และ Update Row เดิมเมื่อเกิด Duplicate Primary/Unique Key ผ่าน INSERT ... ON DUPLICATE KEY UPDATE.
oxmysql รองรับ Upsert ไหม
รองรับ และ Documentation ของ oxmysql แนะนำ Upsert โดยใช้ MySQL.prepare.
Upsert ต้อง SELECT ก่อนหรือไม่
ไม่ หากเป้าหมายคือแค่ตัดสินว่าจะ Insert หรือ Update oxmysql แนะนำให้ใช้ Duplicate Key Upsert แทน Check-before-write.
Upsert ต้องใช้ Unique Key หรือไม่
ต้องมี Primary/Unique Key Conflict ที่ใช้ตัดสิน Duplicate.
VALUES(theme) หมายถึงอะไร
หมายถึงค่า theme จากส่วน INSERT ที่กำลังจะถูกใช้ใน Statement นั้น.
Upsert ต่างจาก UPDATE อย่างไร
UPDATE แก้เฉพาะ Row ที่มีอยู่ ส่วน Upsert สามารถ Insert หากยังไม่มีและ Update เมื่อมี Duplicate Key
Upsert ต่างจาก INSERT IGNORE อย่างไร
INSERT IGNORE ไม่ได้ Update Row เดิมแบบ ON DUPLICATE KEY UPDATE; มันจัดการ Error/Warnings ในลักษณะ Ignore ตามกฎของ MariaDB.
Upsert ต่างจาก REPLACE INTO อย่างไร
Upsert Update Row เดิม ส่วน REPLACE จะ Delete Row ที่ชนแล้ว Insert ใหม่.
Upsert แทน Transaction ได้ไหม
ไม่ได้ Transaction ใช้เมื่อหลาย Queries ต้อง Commit พร้อมกันทั้งหมด.
Upsert แทน Cache ได้ไหม
ไม่ได้ Upsert ยังเป็น Database Write ส่วน Cache ใช้ลดการเข้าถึง Database ซ้ำระหว่าง Runtime
Upsert ใช้ทุก Frame ได้ไหม
ไม่ควร Database Persistent Writes ควรเกิดตาม State Change/Save Strategy ไม่ใช่ทุก Render/Game Tick
Upsert ทำ Server เร็วขึ้นไหม
สามารถลด Check-before-write Query Pattern ได้ แต่ Performance จริงขึ้นกับ Database และ Workload จึงต้อง Benchmark.
ประเด็นสำคัญ
FiveM MySQL Upsert เหมาะเมื่อ Resource มี Requirement:
ไม่มี Row
→ INSERT
มี Row เดิม
→ UPDATE
โดยใช้:
INSERT ...
ON DUPLICATE KEY UPDATE ...
MariaDB จะตัดสิน Duplicate จาก PRIMARY KEY หรือ UNIQUE KEY และ oxmysql แนะนำ Pattern นี้โดยตรง แทนการ Query ตรวจว่ามี Row ก่อนแล้วค่อยเลือก Insert หรือ Update.
สำหรับ Resource ที่เรียก Save Pattern นี้บ่อย สามารถใช้ MySQL.prepare พร้อม ? Placeholders ได้ แต่ Prepare ไม่ได้แทน Unique Constraint และ Upsert ก็ไม่ได้แทน Cache หรือ Transaction.
สิ่งที่ต้องระวังมากคือ REPLACE INTO ไม่เหมือน Upsert เพราะ MariaDB จะลบ Row เดิมก่อนแล้ว Insert ใหม่เมื่อชน Key ขณะที่ ON DUPLICATE KEY UPDATE Update Row เดิม และ oxmysql ระบุว่า Upsert เป็นแนวทางที่ต้องการมากกว่าสำหรับ Use Case นี้.
สำหรับผู้อ่าน comsiam ให้จำสูตร “Unique Key → INSERT → Duplicate → UPDATE” และ comsiam แนะนำให้ใช้ Upsert กับข้อมูลประเภท Settings, Metadata หรือ Persistent Preferences ที่ควรสร้างเองเมื่อยังไม่มี แต่ต้องออกแบบ Unique Key ให้ถูกก่อนเสมอ เพราะหัวใจของ Upsert ไม่ใช่เพียง SQL Statement แต่คือ Schema ที่บอก Database ได้อย่างชัดเจนว่า Record ไหนคือข้อมูลชุดเดียวกัน
Comments
Post a Comment