ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว
บันทึกสิ่งที่เกิดขึ้นโดยไม่เขียนทับ
ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว เป็นบทเรียน SQL Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน SQL Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส SQL Academy มีบทเรียนทั้งหมด 4 บทเรียน
ตารางแบบเพิ่มอย่างเดียวคืออะไร
ตารางเหตุการณ์แบบเพิ่มอย่างเดียวคือตารางที่มีการเพิ่มแถวเท่านั้น — ไม่เคยแก้ไขหรือลบแถว แต่ละแถวแสดงสิ่งที่เกิดขึ้น ณ ช่วงเวลาใดเวลาหนึ่ง
รูปแบบนี้เป็นรากฐานของการจัดเก็บที่ขับเคลื่อนด้วยเหตุการณ์ แทนที่จะเก็บสถานะปัจจุบัน คุณจะเก็บทุกการเปลี่ยนแปลงเป็นเหตุการณ์ที่เปลี่ยนแปลงไม่ได้ ทำให้มีประวัติที่ครบถ้วนและตรวจสอบได้
การสร้างตารางเหตุการณ์
ตารางเหตุการณ์ที่ออกแบบมาอย่างดีจะบันทึกว่าใครทำอะไรกับทรัพยากรใดและเมื่อใด คอลัมน์ occurred_atบันทึกเวลาประทับที่แน่นอน และ DEFAULT NOW()ช่วยให้คอลัมน์นี้มีค่าโดยอัตโนมัติเสมอ
สังเกตว่าไม่มี UPDATE หรือ DELETE อยู่ในการออกแบบนี้ — แถวจะคงอยู่ถาวรทันทีที่เขียนลงไป
CREATE TABLE account_events (
id BIGSERIAL PRIMARY KEY,
account_id BIGINT NOT NULL,
event_type TEXT NOT NULL,
payload JSONB,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);การเพิ่มเหตุการณ์
ทุกการกระทำของผู้ใช้ — การเข้าสู่ระบบ การฝากเงิน หรือการเปลี่ยนอีเมล — จะกลายเป็นแถวใหม่ คุณจะไม่ย้อนกลับไปแก้ไขเหตุการณ์ก่อนหน้า หากต้องแก้ไขบางสิ่ง ให้เพิ่มเหตุการณ์ชดเชยแทน
วิธีนี้ช่วยรักษาลำดับเหตุการณ์ทั้งหมดที่เกิดขึ้นตามลำดับเวลา
INSERT INTO account_events (account_id, event_type, payload)
VALUES
(42, 'account_opened', '{"plan": "free"}'),
(42, 'email_verified', '{"email": "alice@example.com"}'),
(42, 'plan_upgraded', '{"from": "free", "to": "pro"}');การอ่านประวัติทั้งหมด
เนื่องจากมีการจัดเก็บการเปลี่ยนแปลงสถานะแต่ละครั้งไว้เป็นแถว การเรียกดูประวัติทั้งหมดของบัญชีจึงทำได้ด้วย SELECT ที่เรียงตามเวลาอย่างง่ายดาย คุณสามารถประมวลเหตุการณ์ทั้งหมดของระเบียนซ้ำได้ ตั้งแต่เหตุการณ์แรกสุดจนถึงเหตุการณ์ล่าสุด
SELECT
id,
event_type,
payload,
occurred_at
FROM account_events
WHERE account_id = 42
ORDER BY occurred_at ASC;การคำนวณสถานะปัจจุบัน
สำหรับตารางแบบเพิ่มอย่างเดียว คุณจะไม่จัดเก็บสถานะปัจจุบันโดยตรง แต่จะคำนวณสถานะนั้นจากเหตุการณ์ล่าสุดที่เกี่ยวข้อง ในที่นี้ แผนปัจจุบันของบัญชีหมายเลข 42 คือค่าที่ระบุโดยเหตุการณ์ plan_upgraded หรือ account_opened ล่าสุด
การใช้ ORDER BY occurred_at DESC LIMIT 1ช่วยดึงภาพสถานะล่าสุดได้อย่างมีประสิทธิภาพ
SELECT payload->>'to' AS current_plan
FROM account_events
WHERE account_id = 42
AND event_type IN ('account_opened', 'plan_upgraded')
ORDER BY occurred_at DESC
LIMIT 1;การบังคับใช้การเปลี่ยนแปลงไม่ได้ด้วยกฎ
คุณสามารถบังคับใช้คุณสมบัติการเพิ่มอย่างเดียวในระดับฐานข้อมูลได้โดยใช้ RULE ซึ่งจะเพิกเฉยต่อ UPDATE หรือ DELETE ที่เกิดขึ้นกับตารางโดยไม่แจ้งให้ทราบ วิธีนี้ป้องกันการเปลี่ยนแปลงข้อมูลโดยไม่ได้ตั้งใจจากแอปพลิเคชันใดก็ตามที่มีสิทธิ์เขียนข้อมูล
ทริกเกอร์ที่ทำให้เกิดข้อยกเว้นเป็นทางเลือกที่เข้มงวดยิ่งกว่า เนื่องจากจะปฏิเสธการดำเนินการนั้นโดยตรงพร้อมแจ้งข้อผิดพลาด
CREATE RULE no_update_events AS
ON UPDATE TO account_events
DO INSTEAD NOTHING;
CREATE RULE no_delete_events AS
ON DELETE TO account_events
DO INSTEAD NOTHING;การทำให้เปลี่ยนแปลงไม่ได้ด้วยทริกเกอร์
ทริกเกอร์ที่ทำให้เกิดข้อยกเว้นเข้มงวดกว่ากฎที่ทำงานโดยไม่แจ้งให้ทราบ — แอปพลิเคชันจะได้รับข้อผิดพลาดทันทีหากพยายามเปลี่ยนแปลงเหตุการณ์ในอดีต วิธีนี้ทำให้ข้อบกพร่องปรากฏให้เห็น แทนที่จะถูกกลืนหายไปโดยไม่มีการแจ้งเตือน
CREATE OR REPLACE FUNCTION deny_event_mutation()
RETURNS TRIGGER AS $$
BEGIN
RAISE EXCEPTION 'Event table is append-only: % is not allowed', TG_OP;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_deny_event_mutation
BEFORE UPDATE OR DELETE ON account_events
FOR EACH ROW EXECUTE FUNCTION deny_event_mutation();การนับเหตุการณ์ตามช่วงเวลา
ตารางแบบเพิ่มอย่างเดียวทำให้การวิเคราะห์ตามเวลาเป็นเรื่องตรงไปตรงมา เนื่องจากทุกเหตุการณ์มีเวลาประทับ คุณจึงจัดกลุ่มตามวัน สัปดาห์ หรือเดือนได้โดยไม่ต้องมีคอลัมน์เพิ่มเติม ตัวอย่างด้านล่างนับจำนวนเหตุการณ์แต่ละประเภทที่เกิดขึ้นในแต่ละวัน
SELECT
DATE_TRUNC('day', occurred_at) AS day,
event_type,
COUNT(*) AS total
FROM account_events
GROUP BY 1, 2
ORDER BY 1, 2;การสร้างสถานะย้อนหลัง ณ เวลาใดเวลาหนึ่ง
คุณสมบัติที่ทรงพลังอย่างหนึ่งของบันทึกเหตุการณ์คือความสามารถในการสร้างสถานะของระเบียนใด ๆ กลับขึ้นมาตามที่เคยมีอยู่ในช่วงเวลาใดเวลาหนึ่งในอดีต เพียงกรองเหตุการณ์จนถึงเวลาประทับที่ต้องการ — ไม่จำเป็นต้องใช้ส่วนขยายสำหรับย้อนเวลา
ความสามารถนี้มีประโยชน์อย่างยิ่งต่อการแก้ไขจุดบกพร่อง การตรวจสอบ และการปฏิบัติตามข้อกำกับดูแล
-- What plan was account 42 on at the end of last month?
SELECT payload->>'to' AS plan_at_snapshot
FROM account_events
WHERE account_id = 42
AND event_type IN ('account_opened', 'plan_upgraded')
AND occurred_at <= DATE_TRUNC('month', NOW()) - INTERVAL '1 second'
ORDER BY occurred_at DESC
LIMIT 1;การใช้เหตุการณ์ชดเชยแทนการแก้ไข
เมื่อพบข้อผิดพลาด — เช่น การเรียกเก็บเงินไม่ถูกต้อง — คุณจะไม่ลบเหตุการณ์ที่ไม่ถูกต้อง แต่ให้เพิ่มเหตุการณ์ชดเชยเพื่อยกเลิกหรือย้อนกลับผลของเหตุการณ์นั้น เหตุการณ์ทั้งสองจะยังคงปรากฏในบันทึก แสดงให้เห็นอย่างชัดเจนว่าเกิดอะไรขึ้นและแก้ไขเมื่อใด
วิธีนี้ช่วยให้ร่องรอยการตรวจสอบครบถ้วนและแสดงให้เห็นได้หากมีการแก้ไขโดยมิชอบ
-- A charge was applied by mistake; record a reversal
INSERT INTO account_events (account_id, event_type, payload)
VALUES (
42,
'charge_reversed',
'{"reason": "billing_error", "reverses_event_id": 17}'
);การแบ่งตารางเหตุการณ์ขนาดใหญ่เป็นพาร์ติชัน
ตารางเหตุการณ์เติบโตอย่างรวดเร็ว การแบ่งตามช่วงเวลาช่วยให้แต่ละพาร์ติชันมีขนาดเล็กลง ทำให้คำสั่งสอบถามตามช่วงทำงานเร็วขึ้น และเปิดโอกาสให้จัดเก็บพาร์ติชันเก่าถาวรหรือลบออกได้โดยไม่ต้องแตะต้องข้อมูลล่าสุด
การแบ่งพาร์ติชันแบบประกาศของ PostgreSQL ทำให้เรื่องนี้ตรงไปตรงมา เพียงกำหนดพาร์ติชันแบบ RANGE บน occurred_at แล้วให้ฐานข้อมูลจัดเส้นทางการเพิ่มแถวโดยอัตโนมัติ
CREATE TABLE account_events_2025
PARTITION OF account_events
FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');
CREATE TABLE account_events_2026
PARTITION OF account_events
FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');ตรวจสอบความเข้าใจเกี่ยวกับตารางแบบเพิ่มอย่างเดียว
ทดสอบความเข้าใจเกี่ยวกับการออกแบบตารางเหตุการณ์แบบเพิ่มอย่างเดียว
ทบทวน: ตารางเหตุการณ์แบบเพิ่มอย่างเดียว
ในบทเรียนนี้ คุณได้เรียนรู้วิธีออกแบบและใช้ตารางเหตุการณ์แบบเพิ่มอย่างเดียว:
- การเปลี่ยนแปลงไม่ได้ — แถวจะถูกเพิ่มเพียงครั้งเดียวและไม่เคยถูกแก้ไข เหตุการณ์ในอดีตคือข้อเท็จจริง
- ประวัติที่ครบถ้วน — การเปลี่ยนแปลงสถานะทุกครั้งได้รับการเก็บรักษาไว้ ทำให้สร้างร่องรอยการตรวจสอบทั้งหมดและคำสั่งสอบถาม ณ เวลาใดเวลาหนึ่งได้
- เหตุการณ์ชดเชย — แก้ไขข้อผิดพลาดด้วยการเพิ่มเหตุการณ์ย้อนกลับใหม่ ไม่ใช่การลบเหตุการณ์เดิม
- การบังคับใช้ — กฎหรือทริกเกอร์ในระดับฐานข้อมูลช่วยป้องกันการเปลี่ยนแปลงข้อมูลโดยไม่ได้ตั้งใจ
- ความสามารถในการขยายระบบ — การแบ่งพาร์ติชันตามช่วงช่วยให้บันทึกเหตุการณ์ขนาดใหญ่ยังคงมีประสิทธิภาพเมื่อเวลาผ่านไป
ตารางแบบเพิ่มอย่างเดียวเป็นแกนหลักของการจัดเก็บที่ขับเคลื่อนด้วยเหตุการณ์ สถาปัตยกรรม CQRS และทุกระบบที่การตรวจสอบได้กับความถูกต้องของประวัติมีความสำคัญ
คำถามที่พบบ่อย
บทเรียน “ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส SQL Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส SQL Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว”
บันทึกสิ่งที่เกิดขึ้นโดยไม่เขียนทับ คุณปฏิบัติ SQL Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน SQL Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน SQL Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน SQL Academy นี้ได้ไหม
ได้ บทเรียน SQL Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- เหตุผลที่ควรเก็บประวัติ
- ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว
- แถวแบบเวลาสัมพันธ์และแบบมีรุ่น
- การสร้างสถานะขึ้นใหม่จากเหตุการณ์