0Pricing
SQL Academy · บทเรียน

ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว

บันทึกสิ่งที่เกิดขึ้นโดยไม่เขียนทับ

ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. เหตุผลที่ควรเก็บประวัติ
  2. ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว
  3. แถวแบบเวลาสัมพันธ์และแบบมีรุ่น
  4. การสร้างสถานะขึ้นใหม่จากเหตุการณ์
← กลับไปที่ SQL Academy