PostgreSQL JSONB คู่มือฉบับสมบูรณ์ 2026 — คู่มือฉบับสมบูรณ์ 2026 | SiamCafe Blog

PostgreSQL JSONB คู่มือฉบับสมบูรณ์ 2026 — คู่มือฉบับสมบูรณ์ 2026 | SiamCafe Blog

บทนำ: PostgreSQL JSONB คืออะไร และเหตุใดจึงสำคัญในปี 2026

ในยุคที่ข้อมูลมีความหลากหลายและซับซ้อนมากขึ้น ฐานข้อมูลเชิงสัมพันธ์แบบดั้งเดิม (RDBMS) อย่าง PostgreSQL ได้พัฒนาเพื่อรองรับข้อมูลที่ไม่มีโครงสร้างตายตัว (Semi-structured Data) ผ่านชนิดข้อมูล JSONB (JSON Binary) ซึ่งเป็นรูปแบบการจัดเก็บข้อมูล JSON ในรูปแบบไบนารีที่ได้รับการปรับแต่งประสิทธิภาพสูง

JSONB แตกต่างจาก JSON ทั่วไปตรงที่ JSONB จะถูกแปลงเป็นรูปแบบไบนารีเมื่อถูกจัดเก็บ ทำให้สามารถสร้างดัชนี (Index) ค้นหา และจัดการข้อมูลได้อย่างมีประสิทธิภาพมากกว่า ในปี 2026 PostgreSQL เวอร์ชัน 18 ได้เพิ่มความสามารถใหม่ๆ เช่น JSONB Path Query ที่รองรับ SQL/JSON Path Language อย่างเต็มรูปแบบ และการปรับปรุง Partial Index สำหรับ JSONB โดยเฉพาะ

บทความนี้จะพาคุณเจาะลึกทุกแง่มุมของ PostgreSQL JSONB ตั้งแต่พื้นฐานจนถึงเทคนิคขั้นสูง พร้อมตัวอย่างการใช้งานจริงที่คุณสามารถนำไปปรับใช้ได้ทันที

1. พื้นฐาน JSONB: การสร้าง การแทรก และการสืบค้น

1.1 การสร้างตารางที่มีคอลัมน์ JSONB

การประกาศคอลัมน์ชนิด JSONB ทำได้ง่ายมาก เพียงใช้คำสั่ง CREATE TABLE ดังตัวอย่าง:

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    attributes JSONB NOT NULL DEFAULT '{}',
    created_at TIMESTAMP DEFAULT NOW()
);

-- เพิ่มข้อมูลตัวอย่าง
INSERT INTO products (name, attributes) VALUES
('Laptop Pro 15', '{"brand": "TechCo", "price": 45000, "specs": {"cpu": "i7-13700H", "ram": "16GB", "storage": "512GB SSD"}}'),
('Wireless Mouse', '{"brand": "TechCo", "price": 1200, "specs": {"type": "bluetooth", "dpi": 1600}, "colors": ["black", "white"]}'),
('USB-C Hub', '{"brand": "Accessories Inc", "price": 2500, "specs": {"ports": ["HDMI", "USB-A", "USB-C", "SD"]}}');

1.2 การสืบค้นข้อมูล JSONB ด้วยโอเปอเรเตอร์พื้นฐาน

PostgreSQL มีโอเปอเรเตอร์สำหรับ JSONB ที่ทรงพลังหลายตัว ดังตารางเปรียบเทียบด้านล่าง:

โอเปอเรเตอร์ คำอธิบาย ตัวอย่าง
-> เข้าถึงฟิลด์ JSON และคืนค่าเป็น JSON (รวมถึง object/array) attributes -> 'brand'"TechCo"
->> เข้าถึงฟิลด์ JSON และคืนค่าเป็น TEXT attributes ->> 'brand'TechCo
#> เข้าถึงฟิลด์แบบซ้อน (path) คืนค่าเป็น JSON attributes #> '{specs, cpu}'"i7-13700H"
#>> เข้าถึงฟิลด์แบบซ้อน (path) คืนค่าเป็น TEXT attributes #>> '{specs, cpu}'i7-13700H
@> ตรวจสอบว่า JSONB ด้านซ้ายมีค่า JSONB ด้านขวาหรือไม่ (containment) attributes @> '{"brand": "TechCo"}'
? ตรวจสอบว่ามีคีย์ที่กำหนดหรือไม่ attributes ? 'colors'
?| ตรวจสอบว่ามีคีย์ใดคีย์หนึ่งในอาร์เรย์ที่กำหนดหรือไม่ attributes ?| array['brand', 'color']
?& ตรวจสอบว่ามีทุกคีย์ในอาร์เรย์ที่กำหนดหรือไม่ attributes ?& array['brand', 'specs']

ตัวอย่างการสืบค้นข้อมูล:

-- ค้นหาสินค้าที่มี brand เป็น TechCo
SELECT name, attributes ->> 'price' AS price
FROM products
WHERE attributes @> '{"brand": "TechCo"}';

-- ค้นหาสินค้าที่มีราคามากกว่า 3000 บาท
SELECT name, (attributes ->> 'price')::numeric AS price
FROM products
WHERE (attributes ->> 'price')::numeric > 3000;

-- ค้นหาสินค้าที่มี CPU เป็น i7 (ใช้ Path Query)
SELECT name
FROM products
WHERE attributes #>> '{specs, cpu}' LIKE 'i7%';

2. การสร้างดัชนี (Index) สำหรับ JSONB เพื่อประสิทธิภาพสูงสุด

หนึ่งในจุดแข็งที่สุดของ JSONB คือความสามารถในการสร้างดัชนีหลากหลายประเภท ซึ่งทำให้การค้นหาข้อมูลใน JSON ขนาดใหญ่ทำได้รวดเร็วเทียบเท่ากับคอลัมน์ทั่วไป

2.1 GIN Index (Generalized Inverted Index)

GIN Index เป็นดัชนีที่เหมาะสมที่สุดสำหรับการค้นหาแบบ containment (@>), existence (?, ?|, ?&) และการค้นหาคีย์/ค่าใน JSONB

-- สร้าง GIN Index แบบมาตรฐาน
CREATE INDEX idx_products_attributes_gin 
ON products USING GIN (attributes);

-- สร้าง GIN Index สำหรับ path เฉพาะ (jsonb_path_ops) - มีขนาดเล็กกว่าและเร็วกว่าสำหรับ @>
CREATE INDEX idx_products_attributes_path_ops 
ON products USING GIN (attributes jsonb_path_ops);

ข้อควรระวัง: jsonb_path_ops จะสนับสนุนเฉพาะโอเปอเรเตอร์ @> เท่านั้น แต่จะไม่สนับสนุน ?, ?|, ?&

2.2 BTREE Index สำหรับฟิลด์เฉพาะใน JSONB

หากคุณต้องการค้นหาหรือเรียงลำดับตามฟิลด์เฉพาะใน JSONB อย่างเช่นราคา การสร้าง BTREE Index บน expression จะช่วยได้มาก:

-- สร้าง Index บนฟิลด์ price ที่อยู่ใน JSONB
CREATE INDEX idx_products_price 
ON products (((attributes ->> 'price')::numeric));

-- ใช้งาน: การค้นหาจะใช้ Index นี้
SELECT name, (attributes ->> 'price')::numeric AS price
FROM products
WHERE (attributes ->> 'price')::numeric BETWEEN 1000 AND 5000
ORDER BY (attributes ->> 'price')::numeric DESC;

2.3 Partial Index สำหรับ JSONB

ในปี 2026 PostgreSQL 18 ได้ปรับปรุง Partial Index ให้ทำงานกับ JSONB ได้ดีขึ้น โดยเฉพาะกรณีที่คุณต้องการสร้างดัชนีเฉพาะบางเงื่อนไขเท่านั้น:

-- สร้าง Partial Index เฉพาะสินค้าที่มี brand = 'TechCo'
CREATE INDEX idx_products_techco_attributes 
ON products USING GIN (attributes)
WHERE attributes @> '{"brand": "TechCo"}';

-- สร้าง Partial Index สำหรับฟิลด์ price ที่มีค่าไม่เป็น NULL
CREATE INDEX idx_products_price_not_null 
ON products (((attributes ->> 'price')::numeric))
WHERE attributes ? 'price';

3. การจัดการข้อมูล JSONB ขั้นสูง: การอัปเดตและการแปลง

3.1 การอัปเดตฟิลด์ใน JSONB

การอัปเดตข้อมูลใน JSONB มีหลายวิธี ขึ้นอยู่กับความซับซ้อน:

-- วิธีที่ 1: ใช้ || (concatenation) เพื่อเพิ่ม/แทนที่ฟิลด์
UPDATE products
SET attributes = attributes || '{"price": 42000, "discount": 10}'
WHERE name = 'Laptop Pro 15';

-- วิธีที่ 2: ใช้ jsonb_set() เพื่ออัปเดตฟิลด์ที่ซ้อนกัน
UPDATE products
SET attributes = jsonb_set(
    attributes,
    '{specs, ram}',
    '"32GB"'
)
WHERE name = 'Laptop Pro 15';

-- วิธีที่ 3: ใช้ jsonb_set() สำหรับ path แบบหลายระดับ
UPDATE products
SET attributes = jsonb_set(
    attributes,
    '{specs, storage}',
    '"1TB SSD"'
)
WHERE name = 'Laptop Pro 15';

-- วิธีที่ 4: ลบฟิลด์ด้วย - (ลบ top-level key)
UPDATE products
SET attributes = attributes - 'discount'
WHERE name = 'Laptop Pro 15';

-- วิธีที่ 5: ลบฟิลด์ซ้อนด้วย #- (path)
UPDATE products
SET attributes = attributes #- '{specs, storage}'
WHERE name = 'Laptop Pro 15';

3.2 การแปลง JSONB เป็นตาราง (Row Expansion)

บางครั้งเราต้องการแปลง JSONB ให้เป็นรูปแบบตารางเพื่อใช้ร่วมกับฟังก์ชันอื่นๆ:

-- ใช้ jsonb_to_record() เพื่อแปลง JSONB เป็น row
SELECT *
FROM products,
LATERAL jsonb_to_record(attributes) AS x(
    brand TEXT,
    price NUMERIC,
    specs JSONB
)
WHERE brand = 'TechCo';

-- ใช้ jsonb_each() เพื่อวนลูป object
SELECT name, key, value
FROM products,
LATERAL jsonb_each(attributes)
WHERE name = 'Laptop Pro 15';

-- ใช้ jsonb_array_elements() สำหรับ array
SELECT name, color
FROM products,
LATERAL jsonb_array_elements(attributes -> 'colors') AS color
WHERE attributes ? 'colors';

4. การเปรียบเทียบ JSONB กับ JSON และกับคอลัมน์แบบดั้งเดิม

ตารางเปรียบเทียบด้านล่างจะช่วยให้คุณตัดสินใจได้ว่าเมื่อใดควรใช้ JSONB:

คุณสมบัติ JSON (ข้อความ) JSONB (ไบนารี) คอลัมน์แบบดั้งเดิม
ความเร็วในการจัดเก็บ เร็ว (เก็บตามที่ป้อน) ช้ากว่าเล็กน้อย (ต้องแปลงเป็นไบนารี) เร็วมาก
ความเร็วในการสืบค้น ช้า (ต้อง parse ทุกครั้ง) เร็วมาก (รองรับ Index) เร็วมาก
รองรับ Index ไม่รองรับ GIN, BTREE, GiST ทุกประเภท
รักษาลำดับคีย์ ใช่ (รักษาลำดับเดิม) ไม่ (เรียงตามไบนารี) N/A
พื้นที่จัดเก็บ มากกว่า (รวม whitespace) น้อยกว่า (บีบอัด) น้อยที่สุด
ความยืดหยุ่นของ Schema สูงมาก สูงมาก ต่ำ (ต้องกำหนดล่วงหน้า)
การตรวจสอบความถูกต้อง เฉพาะ syntax JSON เฉพาะ syntax JSON ตามประเภทข้อมูลที่กำหนด
กรณีการใช้งาน เก็บ log, ข้อมูลชั่วคราว ข้อมูลที่ต้องค้นหาบ่อย, metadata ข้อมูลที่มีโครงสร้างแน่นอน

ข้อแนะนำ: ใช้ JSONB เมื่อคุณต้องการความยืดหยุ่นของ Schema และต้องค้นหาข้อมูลภายใน JSON บ่อยครั้ง แต่ถ้าข้อมูลของคุณมีโครงสร้างที่แน่นอนและไม่เปลี่ยนแปลง การใช้คอลัมน์แบบดั้งเดิมจะให้ประสิทธิภาพที่ดีกว่าและตรวจสอบข้อมูลได้เข้มงวดกว่า

5. Best Practices สำหรับการใช้งาน JSONB ในปี 2026

5.1 การออกแบบ Schema สำหรับ JSONB

  • ใช้ JSONB สำหรับข้อมูลที่เปลี่ยนแปลงบ่อยหรือไม่แน่นอน: เช่น metadata, user preferences, configuration settings
  • หลีกเลี่ยงการเก็บข้อมูลที่มีโครงสร้างซับซ้อนเกินไป: หาก JSONB มีความลึกมากกว่า 3-4 ระดับ ควรพิจารณา Normalize เป็นตารางแยก
  • ตั้งชื่อฟิลด์ให้สม่ำเสมอ: ใช้ snake_case หรือ camelCase แต่เลือกเพียงแบบเดียวตลอดทั้งระบบ
  • ใช้ Not Null Constraint: กำหนด DEFAULT '{}' เสมอเพื่อป้องกัน NULL

5.2 การเพิ่มประสิทธิภาพการสืบค้น

  1. สร้าง GIN Index เสมอ สำหรับคอลัมน์ JSONB ที่มีการค้นหาด้วย @> หรือ ?
  2. ใช้ Partial Index สำหรับข้อมูลที่มีเงื่อนไขเฉพาะ เช่น สินค้าที่มี stock > 0
  3. หลีกเลี่ยงการใช้ LIKE บน JSONB ให้ใช้ @> หรือ jsonb_path_exists() แทน
  4. ใช้ EXPLAIN ANALYZE เพื่อตรวจสอบว่า Index ถูกใช้งานจริงหรือไม่

5.3 การจัดการข้อมูลขนาดใหญ่

  • จำกัดขนาดของ JSONB: ไม่ควรเกิน 1-2 MB ต่อ record หากใหญ่กว่านี้ควรเก็บในตารางแยก
  • ใช้ Partitioning: สำหรับตารางที่มี JSONB หลายล้าน record ควรพิจารณา Table Partitioning ตามวันที่หรือตามค่าของฟิลด์
  • ใช้ TOAST อย่างเหมาะสม: PostgreSQL จะบีบอัด JSONB โดยอัตโนมัติเมื่อมีขนาดเกิน 2KB

6. กรณีการใช้งานจริง (Real-World Use Cases)

6.1 ระบบ E-commerce: การจัดการคุณสมบัติสินค้าที่หลากหลาย

ร้านค้าออนไลน์มักมีสินค้าหลายประเภท แต่ละประเภทมีคุณสมบัติแตกต่างกัน เช่น เสื้อผ้ามีขนาดและสี ในขณะที่อุปกรณ์อิเล็กทรอนิกส์มีสเปกที่ซับซ้อน JSONB เหมาะกับกรณีนี้:

-- สร้างตารางสินค้าที่มี attributes เป็น JSONB
CREATE TABLE ecommerce_products (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    category_id INT NOT NULL,
    name VARCHAR(255) NOT NULL,
    attributes JSONB NOT NULL DEFAULT '{}',
    created_at TIMESTAMP DEFAULT NOW()
);

-- สร้าง GIN Index
CREATE INDEX idx_ecom_attr_gin ON ecommerce_products USING GIN (attributes);

-- ตัวอย่างข้อมูลสินค้าประเภทต่างๆ
INSERT INTO ecommerce_products (category_id, name, attributes) VALUES
(1, 'Cotton T-Shirt', '{"size": ["S", "M", "L", "XL"], "color": ["red", "blue", "black"], "material": "100% cotton", "care": "machine washable"}'),
(2, 'Smartphone X', '{"brand": "PhoneCo", "screen": "6.5 inch AMOLED", "ram": "8GB", "storage": ["128GB", "256GB"], "camera": {"main": "48MP", "ultrawide": "12MP"}, "battery": "4500mAh"}'),
(3, 'Coffee Maker', '{"brand": "BrewCo", "capacity": "12 cups", "features": ["programmable", "auto-shutoff", "thermal carafe"], "power": "900W"}');

-- ค้นหาสินค้าที่มี RAM 8GB (ใช้ Path Query)
SELECT name, attributes ->> 'brand' AS brand
FROM ecommerce_products
WHERE attributes @> '{"ram": "8GB"}';

-- ค้นหาสินค้าที่มีฟีเจอร์ "programmable"
SELECT name
FROM ecommerce_products
WHERE attributes @> '{"features": ["programmable"]}';

6.2 ระบบบันทึกกิจกรรม (Activity Logging) และ Audit Trail

ระบบที่ต้องบันทึกการเปลี่ยนแปลงข้อมูล (Audit Log) มักมีโครงสร้างที่ไม่แน่นอน JSONB ช่วยให้เก็บข้อมูลการเปลี่ยนแปลงได้อย่างยืดหยุ่น:

-- สร้างตาราง Audit Log
CREATE TABLE audit_logs (
    id BIGSERIAL PRIMARY KEY,
    table_name VARCHAR(100) NOT NULL,
    record_id UUID NOT NULL,
    action VARCHAR(20) NOT NULL, -- INSERT, UPDATE, DELETE
    old_data JSONB,
    new_data JSONB,
    changed_by VARCHAR(100),
    changed_at TIMESTAMP DEFAULT NOW()
);

-- สร้าง Index เพื่อค้นหาตาม table และ record
CREATE INDEX idx_audit_table_record ON audit_logs (table_name, record_id);
CREATE INDEX idx_audit_changed_at ON audit_logs (changed_at);
CREATE INDEX idx_audit_old_new ON audit_logs USING GIN (old_data, new_data);

-- ตัวอย่างการบันทึก
INSERT INTO audit_logs (table_name, record_id, action, old_data, new_data, changed_by) VALUES
('users', 'a1b2c3d4-...', 'UPDATE', 
 '{"name": "John Doe", "email": "[email protected]", "role": "user"}',
 '{"name": "John Doe", "email": "[email protected]", "role": "admin"}',
 '[email protected]');

-- ค้นหาว่าใครเปลี่ยน role เป็น admin บ้าง
SELECT changed_by, changed_at
FROM audit_logs
WHERE table_name = 'users'
  AND new_data @> '{"role": "admin"}'
  AND old_data @> '{"role": "user"}';

6.3 ระบบการตั้งค่าผู้ใช้ (User Preferences) แบบยืดหยุ่น

แอปพลิเคชันสมัยใหม่ต้องการให้ผู้ใช้ปรับแต่งการตั้งค่าได้หลากหลาย JSONB ช่วยให้จัดเก็บ preferences ที่แตกต่างกันในแต่ละผู้ใช้ได้โดยไม่ต้องสร้างตารางแยก:

-- สร้างตารางผู้ใช้ที่มี preferences เป็น JSONB
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) UNIQUE NOT NULL,
    preferences JSONB NOT NULL DEFAULT '{}',
    created_at TIMESTAMP DEFAULT NOW()
);

-- ตัวอย่าง preferences ที่แตกต่างกันในแต่ละผู้ใช้
INSERT INTO users (email, preferences) VALUES
('[email protected]', '{
    "theme": "dark",
    "notifications": {"email": true, "push": true, "sms": false},
    "language": "th",
    "timezone": "Asia/Bangkok",
    "dashboard_widgets": ["weather", "calendar", "tasks"]
}'),
('[email protected]', '{
    "theme": "light",
    "notifications": {"email": false, "push": true},
    "language": "en",
    "timezone": "America/New_York"
}');

-- ค้นหาผู้ใช้ที่เปิดการแจ้งเตือนทางอีเมล
SELECT email, preferences ->> 'language' AS lang
FROM users
WHERE preferences @> '{"notifications": {"email": true}}';

-- อัปเดต preferences บางส่วน
UPDATE users
SET preferences = jsonb_set(
    preferences,
    '{notifications, push}',
    'false'
)
WHERE email = '[email protected]';

7. เทคนิคขั้นสูง: JSONB Path Query และฟังก์ชัน SQL/JSON

PostgreSQL 18 รองรับ SQL/JSON Path Language อย่างเต็มรูปแบบ ซึ่งช่วยให้การสืบค้น JSONB ซับซ้อนทำได้ง่ายขึ้น:

7.1 การใช้ jsonb_path_exists() และ jsonb_path_query()

-- ตรวจสอบว่ามีสินค้าที่ราคามากกว่า 40000 บาทหรือไม่
SELECT name
FROM products
WHERE jsonb_path_exists(
    attributes,
    '$.price ? (@ > 40000)'
);

-- ค้นหาสินค้าที่มี CPU เป็น i7 และ RAM มากกว่า 16GB
SELECT name
FROM products
WHERE jsonb_path_exists(
    attributes,
    '$.specs.cpu like_regex "i7" && $.specs.ram like_regex "16GB|32GB|64GB"'
);

-- ดึงข้อมูลเฉพาะฟิลด์ที่ต้องการด้วย jsonb_path_query()
SELECT name, 
       jsonb_path_query(attributes, '$.specs.cpu')::text AS cpu,
       jsonb_path_query(attributes, '$.specs.ram')::text AS ram
FROM products
WHERE jsonb_path_exists(attributes, '$.specs.cpu');

-- ใช้ wildcard สำหรับ array
SELECT name, color
FROM products,
LATERAL jsonb_path_query(attributes, '$.colors[*]') AS color
WHERE jsonb_path_exists(attributes, '$.colors[*]');

7.2 การใช้ jsonb_set() และ jsonb_insert() ขั้นสูง

-- เพิ่ม element ใน array ที่ตำแหน่งที่กำหนด
UPDATE products
SET attributes = jsonb_insert(
    attributes,
    '{specs, ports, 1}',
    '"Thunderbolt 4"'
)
WHERE name = 'USB-C Hub';

-- แทนที่ค่าใน array ด้วยเงื่อนไข
UPDATE products
SET attributes = jsonb_set(
    attributes,
    '{specs, ports}',
    (SELECT jsonb_agg(
        CASE WHEN value = 'USB-A' THEN '"USB-A 3.2"' ELSE value END
     )
     FROM jsonb_array_elements(attributes -> 'specs' -> 'ports') AS value)
)
WHERE name = 'USB-C Hub';

8. การจัดการข้อผิดพลาดและการตรวจสอบความถูกต้อง

8.1 การตรวจสอบ JSONB ก่อนจัดเก็บ

ถึงแม้ว่า JSONB จะไม่ตรวจสอบ Schema แต่คุณสามารถสร้าง CHECK constraint เพื่อบังคับโครงสร้างบางอย่างได้:

-- สร้างตารางที่มี CHECK constraint สำหรับ JSONB
CREATE TABLE validated_products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    attributes JSONB NOT NULL DEFAULT '{}',
    CONSTRAINT valid_product_attrs CHECK (
        attributes ? 'brand' 
        AND attributes ? 'price'
        AND (attributes ->> 'price')::numeric > 0
        AND jsonb_typeof(attributes -> 'specs') = 'object'
    )
);

-- ตัวอย่างการแทรกที่ถูกต้อง
INSERT INTO validated_products (name, attributes) VALUES
('Valid Product', '{"brand": "Test", "price": 100, "specs": {"weight": "1kg"}}');

-- ตัวอย่างที่ผิด (จะเกิด error)
INSERT INTO validated_products (name, attributes) VALUES
('Invalid Product', '{"brand": "Test", "price": -5}'); -- price ติดลบ

8.2 การใช้ฟังก์ชันเพื่อตรวจสอบ JSONB

-- สร้างฟังก์ชันตรวจสอบความถูกต้องของ attributes สำหรับสินค้าอิเล็กทรอนิกส์
CREATE OR REPLACE FUNCTION validate_electronics_attrs(attrs JSONB)
RETURNS BOOLEAN AS $$
BEGIN
    -- ต้องมี brand และ price
    IF NOT (attrs ? 'brand' AND attrs ? 'price') THEN
        RETURN FALSE;
    END IF;
    
    -- price ต้องเป็นตัวเลขบวก
    IF (attrs ->> 'price')::numeric <= 0 THEN
        RETURN FALSE;
    END IF;
    
    -- ถ้ามี specs ต้องเป็น object
    IF attrs ? 'specs' AND jsonb_typeof(attrs -> 'specs') != 'object' THEN
        RETURN FALSE;
    END IF;
    
    RETURN TRUE;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- ใช้งานฟังก์ชันใน CHECK constraint
ALTER TABLE products ADD CONSTRAINT valid_electronics 
CHECK (validate_electronics_attrs(attributes));

9. การปรับแต่งประสิทธิภาพ (Performance Tuning) สำหรับ JSONB

9.1 การวิเคราะห์ Query Plan

การใช้ EXPLAIN ANALYZE ช่วยให้คุณเห็นว่า Index ถูกใช้งานหรือไม่ และมี bottleneck ตรงไหน:

-- ตรวจสอบว่า GIN Index ถูกใช้หรือไม่
EXPLAIN ANALYZE
SELECT name
FROM products
WHERE attributes @> '{"brand": "TechCo"}';

-- ผลลัพธ์ที่ควรเห็น (ถ้าใช้ Index):
-- Bitmap Heap Scan on products
--   Recheck Cond: (attributes @> '{"brand": "TechCo"}'::jsonb)
--   ->  Bitmap Index Scan on idx_products_attributes_gin
--       Index Cond: (attributes @> '{"brand": "TechCo"}'::jsonb)

9.2 การใช้ Parallel Query กับ JSONB

PostgreSQL 18 รองรับ Parallel Query สำหรับ JSONB มากขึ้น โดยเฉพาะกับฟังก์ชัน jsonb_path_query():

-- เปิดใช้งาน Parallel Query (ถ้ายังไม่ได้เปิด)
SET max_parallel_workers_per_gather = 4;

-- คำสั่งที่สามารถใช้ Parallel Query ได้
SELECT name, 
       jsonb_path_query(attributes, '$.specs.*') AS spec_value
FROM products
WHERE jsonb_path_exists(attributes, '$.specs.cpu');

-- ตรวจสอบว่าใช้ Parallel Query หรือไม่
EXPLAIN ANALYZE
SELECT count(*)
FROM products
WHERE attributes @> '{"brand": "TechCo"}';

9.3 การจัดการ TOAST (The Oversized-Attribute Storage Technique)

PostgreSQL จะบีบอัด JSONB โดยอัตโนมัติเมื่อมีขนาดเกิน 2KB การปรับแต่ง TOAST อาจช่วยเพิ่มประสิทธิภาพ:

-- ตรวจสอบขนาด TOAST table
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_stat_user_tables
WHERE relname = 'products';

-- ปรับค่า TOAST threshold (ต้องเป็น superuser)
ALTER TABLE products ALTER COLUMN attributes SET STORAGE EXTENDED; -- ค่าเริ่มต้น

-- หรือใช้ MAIN เพื่อเก็บใน main table ถ้าเป็นไปได้
ALTER TABLE products ALTER COLUMN attributes SET STORAGE MAIN;

10. ความปลอดภัยและการจัดการสิทธิ์สำหรับ JSONB

10.1 การป้องกัน SQL Injection ใน JSONB

ถึงแม้ว่า JSONB จะปลอดภัยกว่า JSON ทั่วไป แต่ก็ยังมีช่องโหว่หากคุณใช้ string concatenation:

-- ไม่ปลอดภัย: ใช้ string concatenation
-- SELECT * FROM products WHERE attributes ->> 'brand' = ''' OR 1=1 --';

-- ปลอดภัย: ใช้ parameterized query
-- ในภาษาโปรแกรม เช่น Python:
-- cursor.execute(
--     "SELECT * FROM products WHERE attributes @> %s",
--     (json.dumps({"brand": user_input}),)
-- )

-- ปลอดภัย: ใช้ function ที่ป้องกัน injection
CREATE OR REPLACE FUNCTION get_products_by_brand(brand_name TEXT)
RETURNS SETOF products AS $$
BEGIN
    RETURN QUERY
    SELECT *
    FROM products
    WHERE attributes @> jsonb_build_object('brand', brand_name);
END;
$$ LANGUAGE plpgsql STABLE;

10.2 การจำกัดการเข้าถึงฟิลด์ใน JSONB

คุณสามารถใช้ Row-Level Security (RLS) เพื่อควบคุมการเข้าถึงข้อมูลใน JSONB:

-- เปิดใช้งาน RLS
ALTER TABLE products ENABLE ROW LEVEL SECURITY;

-- สร้าง policy สำหรับพนักงานขายที่ดูได้เฉพาะสินค้าของตัวเอง
CREATE POLICY sales_policy ON products
FOR SELECT
USING (
    attributes ->> 'sales_rep' = current_user
    OR current_user = 'admin'
);

-- สร้าง policy สำหรับการอัปเดตเฉพาะฟิลด์ที่อนุญาต
CREATE POLICY update_price_policy ON products
FOR UPDATE
USING (current_user = 'price_manager')
WITH CHECK (
    -- อนุญาตให้อัปเดตเฉพาะฟิลด์ price เท่านั้น
    (OLD.attributes - 'price') = (NEW.attributes - 'price')
);

สรุป

PostgreSQL JSONB เป็นเครื่องมือที่ทรงพลังอย่างยิ่งสำหรับการจัดการข้อมูลกึ่งโครงสร้างในปี 2026 ด้วยความสามารถในการสร้างดัชนีที่หลากหลาย การรองรับ SQL/JSON Path Language อย่างเต็มรูปแบบ และการปรับปรุงประสิทธิภาพใน PostgreSQL เวอร์ชัน 18 ทำให้ JSONB กลายเป็นตัวเลือกอันดับต้นๆ สำหรับนักพัฒนาที่ต้องการความยืดหยุ่นของ NoSQL ควบคู่กับความน่าเชื่อถือของฐานข้อมูลเชิงสัมพันธ์

ประเด็นสำคัญที่ควรจดจำ:

  • เลือกใช้ JSONB เมื่อ: ข้อมูลมีโครงสร้างไม่แน่นอน ต้องการความยืดหยุ่นสูง และต้องค้นหาภายใน JSON บ่อยครั้ง
  • สร้าง GIN Index เสมอ: เพื่อให้การค้นหาด้วย @> และ ? มีประสิทธิภาพสูงสุด
  • ใช้ Partial Index และ Expression Index: สำหรับการค้นหาฟิลด์เฉพาะเจาะจง เช่น ราคา หรือวันที่
  • ตรวจสอบความถูกต้องของข้อมูล: ด้วย CHECK constraint หรือฟังก์ชันที่กำหนดเอง เพื่อป้องกันข้อมูลเสีย
  • ใช้ SQL/JSON Path Query: สำหรับการสืบค้นที่ซับซ้อน ซึ่งมีประสิทธิภาพและอ่านง่ายกว่า
  • อย่าเก็บข้อมูลขนาดใหญ่เกินไปใน JSONB: หาก JSONB มีขนาดเกิน 1-2 MB ควรพิจารณา Normalize หรือใช้ TOAST อย่างเหมาะสม

การนำ JSONB ไปใช้อย่างถูกต้องจะช่วยให้ระบบของคุณมีความยืดหยุ่นสูง ลดต้นทุนในการพัฒนา และยังคงรักษาประสิทธิภาพในการทำงานไว้ได้อย่างดีเยี่ยม ไม่ว่าคุณจะกำลังพัฒนา E-commerce platform, ระบบ Audit Log, หรือแอปพลิเคชันที่มีการตั้งค่าผู้ใช้ที่ซับซ้อน JSONB ก็พร้อมจะตอบโจทย์ทุกความท้าทายในยุคข้อมูลปี 2026

บทความนี้เขียนโดยทีมงาน SiamCafe Blog — แหล่งความรู้ด้านเทคโนโลยีและฐานข้อมูลสำหรับนักพัฒนาไทย

จัดส่งรวดเร็วส่งด่วนทั่วประเทศ
รับประกันสินค้าเคลมง่าย มีใบรับประกัน
ผ่อนชำระได้บัตรเครดิต 0% สูงสุด 10 เดือน
สะสมแต้ม รับส่วนลดส่วนลดและคะแนนสะสม

© 2026 SiamLancard — จำหน่ายการ์ดแลน อุปกรณ์ Server และเครื่องพิมพ์ใบเสร็จ

SiamLancard
Logo
Free Forex EA — XM Signal · SiamCafe Blog · SiamLancard · Siam2R · iCafeFX
iCafeForex.com - สอนเทรด Forex | SiamCafe.net
Shopping cart