ข้ามไปยังเนื้อหา

Database, schema และการเชื่อมต่อ

PostgreSQL server เดียวเก็บ table ได้มากกว่าหนึ่งชุด และจัดระเบียบไว้เป็นชั้น ๆ การเข้าใจการซ้อนกันนี้ — cluster, database, schema, table — ช่วยให้กระจ่างว่าข้อมูลของคุณอยู่ที่ไหนและต้องอ้างชื่ออย่างไร บทเรียนนี้จะไล่ลงไปทีละชั้นพร้อมแสดงวิธีสร้างแต่ละชั้น

server หนึ่งที่รันอยู่เรียกว่า cluster cluster หนึ่งบรรจุ database หนึ่งหรือมากกว่า ซึ่งแยกออกจากกันอย่างสมบูรณ์: การเชื่อมต่อหนึ่งสื่อสารกับฐานข้อมูลเพียงหนึ่งเดียวในแต่ละครั้ง ภายในแต่ละ database มี schema ที่เป็นการจัดกลุ่ม table (และ object อื่น ๆ) ที่มีชื่อ ภายในแต่ละ schema มี table เอง แผนภาพแสดงการซ้อนกัน

flowchart TD
  CL[Cluster: one server] --> DB1[Database: appdb]
  CL --> DB2[Database: postgres]
  DB1 --> S1[Schema: public]
  DB1 --> S2[Schema: billing]
  S1 --> T1[Table: customers]
  S1 --> T2[Table: orders]
  S2 --> T3[Table: invoices]
cluster ซ้อน database, schema และ table อย่างไร

database คือหน่วยของการแยกตัว คุณให้แต่ละแอปพลิเคชันมี database ของตัวเองเพื่อให้ table ของแต่ละแอปไม่ชนกัน การสร้างใช้ statement เดียวจบ

CREATE DATABASE shop;

หากต้องการเริ่มใช้ database ใหม่ ให้เชื่อมต่อใหม่เข้าไปที่ database นั้น ใน psql คือ \c shop ส่วนใน driver คุณเปิดการเชื่อมต่อใหม่โดยใช้ shop เป็น database ใน connection string คุณไม่สามารถรัน CREATE DATABASE ภายใน transaction ได้ จึงเป็นเหตุผลที่ตัวอย่าง Python เปิด autocommit

ภายใน database, schema ให้คุณจัดกลุ่ม table ที่เกี่ยวข้องและหลีกเลี่ยงชื่อที่ชนกัน ทุก database ใหม่เริ่มต้นด้วย schema หนึ่งที่มีอยู่แล้ว ชื่อว่า public และเว้นแต่คุณจะระบุเป็นอย่างอื่น table ของคุณจะไปอยู่ที่นั่น

CREATE SCHEMA billing;

ใน pgAdmin: schema ใหม่ปรากฏใต้ Schemas ใน browser tree เมื่อคุณรีเฟรช database คุณยังสร้างได้โดยการคลิกขวาที่ Schemas DBeaver แสดงการจัดกลุ่มเดียวกันใน navigator ของตัวเอง

table มีชื่อเต็มแบบ qualified name ในรูปแบบ schema.table เช่น billing.invoices คุณเขียนชื่อแบบ qualified ได้เสมอและ PostgreSQL จะรู้แน่ชัดว่าคุณหมายถึง table ใด

เมื่อคุณเขียนชื่อแบบ unqualified อย่าง invoices ตัว PostgreSQL จะไล่ดู search_path — รายการ schema ที่เรียงลำดับไว้ — แล้วใช้ตัวที่ตรงกันตัวแรก โดย default แล้ว search path จัด public ไว้ก่อน จึงเป็นเหตุผลที่ table ที่คุณสร้างโดยไม่ระบุ schema จะไปลงที่ public และเรียกกลับมาได้ด้วยชื่อเปล่า ๆ

คุณตรวจสอบและเปลี่ยน search path สำหรับ session ของคุณได้

SHOW search_path;
SET search_path TO billing, public;
search_path
-----------------
"$user", public
(1 row)

เมื่อ billing อยู่ก่อนใน path, invoices แบบ unqualified ตอนนี้จะ resolve ไปยัง billing.invoices การเขียนชื่อแบบ qualified ขจัดความสงสัยทั้งหมดและเป็นนิสัยที่ปลอดภัยที่สุดในฐานข้อมูลที่ใช้ร่วมกัน

สองแนวคิดนี้สับสนกันได้ง่าย database คือภาชนะสำหรับข้อมูลของคุณ ส่วน role คือบัญชีที่เชื่อมต่อและได้รับสิทธิ์ในการกระทำต่อข้อมูลนั้น role เป็นทั้ง user และ group ใน PostgreSQL

ทั้งสองแนวคิดเป็นอิสระต่อกัน: role เดียวสามารถเป็นเจ้าของและเชื่อมต่อกับ database หลายอันได้ และ database หนึ่งก็สามารถถูกใช้โดยหลาย role ได้เช่นกัน

ตลอดโมดูลนี้คุณล็อกอินเป็น role postgres ที่เป็น superuser ระบบจริงจะสร้าง role ที่จำกัดสิทธิ์ต่อแอปพลิเคชันแทน ซึ่งโมดูล operations จะครอบคลุมเรื่องนั้น

  • การเชื่อมต่อมุ่งเป้าไปที่ database เพียงหนึ่งเดียวเสมอ หากต้องการสลับ คุณเชื่อมต่อใหม่
  • database ใหม่มาพร้อม schema public ดังนั้น table ที่คุณสร้างโดยไม่ตั้งชื่อ schema จะไปลงที่ public
  • ชื่อแบบ unqualified ถูก resolve ผ่าน search_path ส่วนรูปแบบ schema.table แบบ qualified ไม่กำกวมและปลอดภัยที่สุดในฐานข้อมูลที่ใช้ร่วมกัน
  • CREATE DATABASE รันภายใน transaction block ไม่ได้ ดังนั้นเครื่องมือต่าง ๆ จึงต้อง commit คำสั่งนี้เดี่ยว ๆ
  • role (บัญชี) และ database (ภาชนะข้อมูล) เป็นแนวคิดที่แยกจากกัน อย่าปฏิบัติต่ออันหนึ่งเสมือนเป็นอีกอัน
ตัวเลือกBenefitCost
หนึ่ง database ต่อหนึ่งแอปแยกตัวชัดเจน, mental model ง่ายconnection pool แยกกัน ใช้ resource มากขึ้น
หลาย schema ในหนึ่ง databaseใช้ connection pool ร่วมกัน, join ข้าม schema ได้ต้องคุม naming และ permission ให้ไม่ชนกันเอง
ชื่อแบบ qualified (schema.table)ไม่กำกวม อ่านแล้วรู้ทันทีว่า table อยู่ที่ไหนเขียนยาวกว่า
พึ่ง search_pathเขียนสั้น กระชับเปราะบาง เพราะผลลัพธ์เปลี่ยนตาม session
  • พึ่ง search_path default ใน database ที่ใช้ร่วมกัน — ถ้ามีหลาย schema ชื่อ table ซ้ำกัน การเขียนชื่อ unqualified อาจ resolve ไปผิด schema โดยไม่มี error แจ้งเตือน
  • เชื่อมต่อด้วย role postgres (superuser) ในสภาพแวดล้อมจริง — ควรสร้าง role ที่จำกัดสิทธิ์เฉพาะแอปพลิเคชัน เพื่อลดความเสียหายถ้า credential รั่วไหล
  • ลืมว่า CREATE DATABASE รันใน transaction block ไม่ได้ — ถ้ารันผ่าน migration tool หรือ script ที่ wrap ทุกอย่างด้วย transaction จะเจอ error ทันที ต้องแยก statement นี้ออกมา commit เดี่ยว ๆ

💡 ตัวอย่างจากของจริง

แพลตฟอร์ม SaaS แบบ multi-tenant — หลายระบบ (เช่นหลาย setup ของ Rails บน Postgres) เลือกใช้ schema-per-tenant ภายใน database เดียว เพื่อให้ได้ความเป็นส่วนตัวของข้อมูลระดับหนึ่งโดยไม่ต้องดูแล database แยกเป็นร้อยเป็นพันตัว

ทีมที่แยก database ต่อแอป — บริษัทจำนวนมากยึดหลักหนึ่ง database ต่อหนึ่ง service เพื่อให้การ scale, backup และ access control ของแต่ละทีมเป็นอิสระต่อกันอย่างสมบูรณ์

schema ใดมีอยู่ในทุก database ใหม่โดย default?
search_path ควบคุมอะไร?
role และ database สัมพันธ์กันอย่างไร?