การ update และ delete
กริยา CRUD สองตัวสุดท้ายเปลี่ยนข้อมูลที่มีอยู่ UPDATE ตั้งค่า column ใหม่ให้ row ที่ตรงเงื่อนไข และ DELETE ลบ row ที่ตรง ทั้งสองพึ่ง clause WHERE ที่คุณเรียนจาก SELECT อย่างมาก — และทั้งสองกลายเป็นความผิดพลาดที่ทำลายล้างถ้าคุณลืมมัน
ทุกอย่างด้านล่างเป็น output จริงจาก PostgreSQL 18.6 (postgres:18.6, aarch64, Docker Desktop บน Mac, กันยายน 2026) รันจากบนลงล่างในฐานข้อมูลเปล่าหนึ่งตัวที่เริ่มด้วยชุดข้อมูลมาตรฐานของคอร์ส การเปลี่ยนแปลงส่วนใหญ่ครอบด้วย BEGIN ... ROLLBACK เพื่อให้ทุกตัวอย่างเริ่มจาก row เดียวกัน command tag (UPDATE 1, DELETE 2) คือสิ่งที่การรันจริงพิมพ์
-- sql/setup.sqlCREATE TABLE authors ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text NOT NULL, born int);CREATE TABLE books ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, title text NOT NULL, author_id bigint REFERENCES authors (id), published int, copies_sold int NOT NULL DEFAULT 0);CREATE TABLE sales ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, book_id bigint REFERENCES books (id), region text NOT NULL, copies int NOT NULL);INSERT INTO authors (name, born) VALUES ('Mara Linde', 1968), ('Owen Pryce', 1975), ('Sela Vance', 1990);INSERT INTO books (title, author_id, published, copies_sold) VALUES ('Quiet Harbors', 1, 2003, 450), ('The Glass Orchard', 1, 2018, 1250), ('Tidal Notes', 2, 2011, 600), ('Unlinked Draft', NULL, 2020, 0);INSERT INTO sales (book_id, region, copies) VALUES (2, 'East', 900), (3, 'West', 400), (1, 'West', 400), (3, 'East', 200), (2, 'West', 150), (1, 'East', 50);การ update column
หัวข้อที่มีชื่อว่า “การ update column”UPDATE ระบุ table ลิสต์คู่ SET column = value และใช้ WHERE เลือก row ถ้าไม่มี WHERE ทุก row ถูกเปลี่ยน ค่าใหม่อ้างถึงค่าเก่าได้ ดังที่ copies_sold + 500 แสดง
-- sql/update-basic.sqlBEGIN;UPDATE booksSET copies_sold = copies_sold + 500WHERE id = 1;SELECT id, title, copies_sold FROM books WHERE id = 1;ROLLBACK;await pool.query( 'UPDATE books SET copies_sold = copies_sold + $1 WHERE id = $2', [500, 1],);cur.execute( "UPDATE books SET copies_sold = copies_sold + %s WHERE id = %s", (500, 1),)_, err := conn.Exec(ctx, "UPDATE books SET copies_sold = copies_sold + $1 WHERE id = $2", 500, 1)sqlx::query("UPDATE books SET copies_sold = copies_sold + $1 WHERE id = $2") .bind(500_i32) .bind(1_i64) .execute(&pool) .await?;BEGINUPDATE 1 id | title | copies_sold----+---------------+------------- 1 | Quiet Harbors | 950(1 row)
ROLLBACKตั้งหลาย column พร้อมกันได้โดยคั่นการกำหนดค่าด้วยเครื่องหมายจุลภาค:
-- sql/update-several-columns.sqlBEGIN;UPDATE books SET published = 2004, copies_sold = 0 WHERE title = 'Unlinked Draft';SELECT id, title, published, copies_sold FROM books WHERE id = 4;ROLLBACK;BEGINUPDATE 1 id | title | published | copies_sold----+----------------+-----------+------------- 4 | Unlinked Draft | 2004 | 0(1 row)
ROLLBACKใน pgAdmin: รันใน Query Tool แผงข้อความรายงาน UPDATE 1 คือจำนวน row ที่เปลี่ยน
ยืนยันการเปลี่ยนแปลงด้วย RETURNING
หัวข้อที่มีชื่อว่า “ยืนยันการเปลี่ยนแปลงด้วย RETURNING”เหมือน INSERT ทั้ง UPDATE และ DELETE รับ RETURNING นี่คือวิธีที่สะอาดที่สุดในการเห็นว่า row ไหนเปลี่ยนและค่าใหม่คืออะไร
-- sql/update-returning.sqlBEGIN;UPDATE booksSET published = 2019WHERE title = 'The Glass Orchard'RETURNING id, title, published;ROLLBACK;const res = await pool.query( 'UPDATE books SET published = $1 WHERE title = $2 RETURNING id, title, published', [2019, 'The Glass Orchard'],);console.log(res.rows);cur.execute( "UPDATE books SET published = %s WHERE title = %s RETURNING id, title, published", (2019, "The Glass Orchard"),)changed = cur.fetchall()rows, err := conn.Query(ctx, "UPDATE books SET published = $1 WHERE title = $2 RETURNING id, title, published", 2019, "The Glass Orchard")let rows = sqlx::query( "UPDATE books SET published = $1 WHERE title = $2 RETURNING id, title, published",).bind(2019_i32).bind("The Glass Orchard").fetch_all(&pool).await?;BEGIN id | title | published----+-------------------+----------- 2 | The Glass Orchard | 2019(1 row)
UPDATE 1ROLLBACKPostgreSQL 18 เพิ่ม alias old และ new ให้ RETURNING ทำให้ statement เดียวรายงานทั้งค่าก่อนและหลังได้ (release notes) ก่อน PostgreSQL 18 ต้อง SELECT ก่อนหรือใช้ trigger:
-- sql/update-returning-old-new.sqlBEGIN;UPDATE booksSET copies_sold = copies_sold + 5WHERE id = 2RETURNING id, title, old.copies_sold AS was, new.copies_sold AS now;ROLLBACK;BEGIN id | title | was | now----+-------------------+------+------ 2 | The Glass Orchard | 1250 | 1255(1 row)
UPDATE 1ROLLBACKUPDATE … FROM
หัวข้อที่มีชื่อว่า “UPDATE … FROM”เพื่อตั้งค่าจาก table อื่น เพิ่ม clause FROM และ join ใน WHERE ที่นี่ copies_sold ของแต่ละหนังสือถูกคำนวณใหม่จาก row ใน sales หนังสือ 1 และ 3 ตรงกับยอดขายอยู่แล้ว หนังสือ 2 มี 200 copy ที่ไม่ปรากฏใน sales (1250 กับ 1050) และ statement จะเขียนทับมันแบบเงียบ ๆ:
-- sql/update-from.sqlBEGIN;UPDATE books bSET copies_sold = s.totalFROM (SELECT book_id, sum(copies) AS total FROM sales GROUP BY book_id) sWHERE s.book_id = b.idRETURNING b.id, b.title, old.copies_sold AS was, new.copies_sold AS now;ROLLBACK;BEGIN id | title | was | now----+-------------------+------+------ 1 | Quiet Harbors | 450 | 450 2 | The Glass Orchard | 1250 | 1050 3 | Tidal Notes | 600 | 600(3 rows)
UPDATE 3ROLLBACKรายการ FROM คือ join และ WHERE คือเงื่อนไข join ทรงพลังและผิดพลาดง่าย (ดูส่วนสุดท้าย)
การลบ row
หัวข้อที่มีชื่อว่า “การลบ row”DELETE FROM ลบ row ที่ตรงกับ clause WHERE เหมือน UPDATE คุณเพิ่ม RETURNING เพื่อดูว่าอะไรถูกลบได้
-- sql/delete-basic.sqlBEGIN;DELETE FROM booksWHERE copies_sold = 0RETURNING id, title;ROLLBACK;const res = await pool.query( 'DELETE FROM books WHERE copies_sold = $1 RETURNING id, title', [0],);cur.execute( "DELETE FROM books WHERE copies_sold = %s RETURNING id, title", (0,),)removed = cur.fetchall()rows, err := conn.Query(ctx, "DELETE FROM books WHERE copies_sold = $1 RETURNING id, title", 0)let rows = sqlx::query("DELETE FROM books WHERE copies_sold = $1 RETURNING id, title") .bind(0_i32) .fetch_all(&pool) .await?;BEGIN id | title----+---------------- 4 | Unlinked Draft(1 row)
DELETE 1ROLLBACKDELETE ... USING คือคู่ของ UPDATE ... FROM สำหรับการลบ: ลบ row ของ sales โดยดูจาก table อื่น (ที่นี่ ทุกยอดขายของหนังสือโดย author 2):
-- sql/delete-using.sqlBEGIN;DELETE FROM sales sUSING books bWHERE s.book_id = b.id AND b.author_id = 2RETURNING s.id, s.region, s.copies;ROLLBACK;BEGIN id | region | copies----+--------+-------- 2 | West | 400 4 | East | 200(2 rows)
DELETE 2ROLLBACKการลบถูกปฏิเสธได้เมื่อ row อื่นยังอ้างอิงเป้าหมายอยู่ ทั้งสอง statement ล้มเหลวด้วย SQLSTATE 23503 และข้อความระบุ constraint กับ table ที่อ้างอิง:
-- @expect-error 23503DELETE FROM books WHERE id = 1;ERROR: update or delete on table "books" violates foreign key constraint "sales_book_id_fkey" on table "sales"DETAIL: Key (id)=(1) is still referenced from table "sales".-- @expect-error 23503DELETE FROM authors WHERE id = 1;ERROR: update or delete on table "authors" violates foreign key constraint "books_author_id_fkey" on table "books"DETAIL: Key (id)=(1) is still referenced from table "books".อันตรายของการลืม WHERE
หัวข้อที่มีชื่อว่า “อันตรายของการลืม WHERE”UPDATE และ DELETE ใช้กับทุก row ที่ตรงเงื่อนไข — และถ้าไม่มีเงื่อนไข ทุก row ตรง DELETE FROM books; จะทำให้ table ว่าง และ UPDATE books SET copies_sold = 0; ทำให้ทุก record เป็นศูนย์ คำเตือนเดียวคือ command tag: UPDATE 4 ทั้งที่คุณคาดหวัง UPDATE 1
นิสัยที่ปลอดภัยคือรัน WHERE เดียวกันเป็น SELECT ก่อน ยืนยันจำนวน row แล้วค่อยเปลี่ยนกริยา การครอบการเปลี่ยนแปลงเสี่ยงด้วย transaction ให้ทางหนี ดังตัวอย่าง (ทั้ง table ถูกทำเป็นศูนย์ แล้วย้อนกลับ):
-- sql/missing-where.sqlSELECT count(*) AS rows_that_match FROM books WHERE author_id = 1;BEGIN;UPDATE books SET copies_sold = 0;SELECT sum(copies_sold) AS total_after FROM books;ROLLBACK;SELECT sum(copies_sold) AS total_after_rollback FROM books; rows_that_match----------------- 2(1 row)
BEGINUPDATE 4 total_after------------- 0(1 row)
ROLLBACK total_after_rollback---------------------- 2300(1 row)รูปแบบเดียวกันจาก psql และ driver — ตรวจภายใน transaction แล้วค่อยตัดสินใจ:
-- sql/transaction-escape-hatch.sqlBEGIN;DELETE FROM sales WHERE book_id = 3;SELECT count(*) AS sales_left FROM sales; -- inspect the result, then decide:ROLLBACK; -- or COMMIT; to keep the changeconst client = await pool.connect();try { await client.query('BEGIN'); await client.query('DELETE FROM sales WHERE book_id = $1', [3]); await client.query('COMMIT');} catch (e) { await client.query('ROLLBACK'); throw e;} finally { client.release();}with conn.transaction(): cur.execute("DELETE FROM sales WHERE book_id = %s", (3,))# commits if the block succeeds, rolls back on exceptiontx, err := conn.Begin(ctx)if err != nil { return err}if _, err := tx.Exec(ctx, "DELETE FROM sales WHERE book_id = $1", 3); err != nil { tx.Rollback(ctx) return err}err = tx.Commit(ctx)let mut tx = pool.begin().await?;sqlx::query("DELETE FROM sales WHERE book_id = $1") .bind(3_i64) .execute(&mut *tx) .await?;tx.commit().await?;BEGINDELETE 2 sales_left------------ 4(1 row)
ROLLBACKภายใน transaction ไม่มีอะไรถาวรจนกว่าคุณ COMMIT และ ROLLBACK ทิ้งการเปลี่ยนแปลง transaction มีโมดูลเต็มของตัวเองภายหลังในคอร์ส snippet ของ driver แต่ละตัวรันหนึ่งครั้งกับสำเนาชั่วคราวของชุดข้อมูล (ตัวที่เป็น transaction commit จริง เหลือ 4 จาก 6 row ของ sales):
$ (each snippet run once against the course dataset in a scratch database, reset before every run; the program prints what it got back)[UPDATE (then SELECT copies_sold of book 1)] Node.js (pg 8.23.0): { copies_sold: 950 }[UPDATE (then SELECT copies_sold of book 1)] Python (psycopg 3.3.6): (950,)[UPDATE (then SELECT copies_sold of book 1)] Go (pgx v5.11.0): 950[UPDATE (then SELECT copies_sold of book 1)] Rust (sqlx 0.9.0): 950[UPDATE ... RETURNING] Node.js (pg 8.23.0): [ { id: '2', title: 'The Glass Orchard', published: 2019 } ][UPDATE ... RETURNING] Python (psycopg 3.3.6): [(2, 'The Glass Orchard', 2019)][UPDATE ... RETURNING] Go (pgx v5.11.0): [2 The Glass Orchard 2019][UPDATE ... RETURNING] Rust (sqlx 0.9.0): 2 The Glass Orchard 2019[DELETE ... RETURNING] Node.js (pg 8.23.0): [ { id: '4', title: 'Unlinked Draft' } ][DELETE ... RETURNING] Python (psycopg 3.3.6): [(4, 'Unlinked Draft')][DELETE ... RETURNING] Go (pgx v5.11.0): [4 Unlinked Draft][DELETE ... RETURNING] Rust (sqlx 0.9.0): 4 Unlinked Draft[DELETE in a transaction (then count(*) of sales)] Node.js (pg 8.23.0): { count: '4' }[DELETE in a transaction (then count(*) of sales)] Python (psycopg 3.3.6): (4,)[DELETE in a transaction (then count(*) of sales)] Go (pgx v5.11.0): 4[DELETE in a transaction (then count(*) of sales)] Rust (sqlx 0.9.0): 4TRUNCATE
หัวข้อที่มีชื่อว่า “TRUNCATE”เพื่อลบทุก row TRUNCATE คือเครื่องมือ: เร็วกว่า DELETE FROM บน table ใหญ่มากและเป็น transactional ใน PostgreSQL มันปฏิเสธเมื่อมี foreign key ชี้มาที่ table เว้นแต่คุณ truncate table ที่อ้างอิงด้วยหรือบอก CASCADE (ซึ่งล้าง table ที่คุณอาจไม่ได้ตั้งใจด้วย):
-- sql/truncate.sqlBEGIN;TRUNCATE sales;SELECT count(*) AS sales_rows FROM sales;ROLLBACK;SELECT count(*) AS sales_rows_after_rollback FROM sales;BEGINTRUNCATE TABLE sales_rows------------ 0(1 row)
ROLLBACK sales_rows_after_rollback--------------------------- 6(1 row)-- @expect-error 0A000TRUNCATE books;ERROR: cannot truncate a table referenced in a foreign key constraintDETAIL: Table "sales" references "books".HINT: Truncate table "sales" at the same time, or use TRUNCATE ... CASCADE.-- sql/truncate-cascade.sqlBEGIN;TRUNCATE books CASCADE;SELECT (SELECT count(*) FROM books) AS books, (SELECT count(*) FROM sales) AS sales, (SELECT count(*) FROM authors) AS authors;ROLLBACK;BEGINNOTICE: truncate cascades to table "sales"TRUNCATE TABLE books | sales | authors-------+-------+--------- 0 | 0 | 3(1 row)
ROLLBACKสิ่งที่ DELETE และ UPDATE ทิ้งไว้
หัวข้อที่มีชื่อว่า “สิ่งที่ DELETE และ UPDATE ทิ้งไว้”PostgreSQL ไม่เคยเขียนทับ row ในที่เดิม UPDATE เขียน row เวอร์ชันใหม่และทิ้งเวอร์ชันเก่าเป็น dead tuple จนกว่า VACUUM จะเก็บกลับ และ DELETE แค่ทำเครื่องหมายว่า row ตาย หลักฐานบน 100,000 row: UPDATE ทั้ง table หนึ่งครั้งทำให้ไฟล์ heap ใหญ่ขึ้นเท่าตัว VACUUM ทำให้พื้นที่ตายนั้นใช้ซ้ำได้ (ไม่ทำให้ไฟล์เล็กลง) และ UPDATE ทั้ง table ครั้งที่สองก็พอดีในพื้นที่เดิม pg_stat_force_next_flush() เป็นตัวช่วยสำหรับการทดสอบที่เผยแพร่สถิติของ session นี้ทันที ปกติจะโผล่ราวหนึ่งวินาทีต่อมา:
-- sql/dead-tuples.sqlCREATE TABLE counters (id int PRIMARY KEY, n int NOT NULL);INSERT INTO counters SELECT g, 0 FROM generate_series(1, 100000) g;VACUUM (ANALYZE) counters;SELECT pg_size_pretty(pg_relation_size('counters')) AS fresh_heap;UPDATE counters SET n = n + 1;SELECT pg_size_pretty(pg_relation_size('counters')) AS after_one_full_update;SELECT pg_stat_force_next_flush();SELECT n_live_tup, n_dead_tup FROM pg_stat_user_tables WHERE relname = 'counters';VACUUM counters;SELECT pg_stat_force_next_flush();SELECT n_live_tup, n_dead_tup FROM pg_stat_user_tables WHERE relname = 'counters';UPDATE counters SET n = n + 1;SELECT pg_size_pretty(pg_relation_size('counters')) AS after_second_update_with_reuse;CREATE TABLEINSERT 0 100000VACUUM fresh_heap------------ 3544 kB(1 row)
UPDATE 100000 after_one_full_update----------------------- 7080 kB(1 row)
pg_stat_force_next_flush--------------------------
(1 row)
n_live_tup | n_dead_tup------------+------------ 200000 | 100000(1 row)
VACUUM pg_stat_force_next_flush--------------------------
(1 row)
n_live_tup | n_dead_tup------------+------------ 100000 | 0(1 row)
UPDATE 100000 after_second_update_with_reuse-------------------------------- 7080 kB(1 row)บท MVCC และบท vacuum จะกลับมาที่เรื่องนี้อย่างลึก ตอนนี้: DELETE ไม่คืนพื้นที่ดิสก์ให้ระบบปฏิบัติการ และ table ที่ update บ่อยต้อง vacuum สม่ำเสมอ (autovacuum ทำให้โดยค่าเริ่มต้น)
Reading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”โจทย์คือ “sync books.copies_sold จากยอดขายภูมิภาค East” ผู้ช่วย AI เขียน UPDATE ... FROM และอ่านแล้วดูดีด้วย:
🐛 หา bug ในโค้ดนี้
-- migration: sync books.copies_sold from the East region sales-- AI comment: "pull the East figures into the books table"UPDATE booksSET copies_sold = s.copiesFROM sales sWHERE s.region = 'East';เฉลย
bug คือไม่มีเงื่อนไข join WHERE กรอง sales เหลือ row ของ East แต่ไม่เคยผูกยอดขายกับหนังสือของมัน หนังสือทุกเล่มจึงถูก join กับทุกยอดขายของ East และ PostgreSQL ใช้ผลจับคู่ตามอำเภอใจหนึ่งค่ากับแต่ละเล่ม มันรันโดยไม่มี error และแตะทุก row รวมถึงหนังสือที่ไม่มียอดขายเลย (UPDATE 4):
-- sql/update-from-bug.sqlBEGIN;UPDATE booksSET copies_sold = s.copiesFROM sales sWHERE s.region = 'East'RETURNING books.id, books.title, old.copies_sold AS was, new.copies_sold AS now;ROLLBACK;BEGIN id | title | was | now----+-------------------+------+----- 1 | Quiet Harbors | 450 | 900 2 | The Glass Orchard | 1250 | 900 3 | Tidal Notes | 600 | 900 4 | Unlinked Draft | 0 | 900(4 rows)
UPDATE 4ROLLBACKหนังสือทุกเล่มตอนนี้อ้างว่ามี 900 copy (ค่าของยอดขาย East แรกที่ join เลือกได้) วิธีแก้คือ join บน key และ aggregate ก่อน แต่ละเล่มจึงได้ค่าเดียว และแตะเฉพาะหนังสือที่มียอดขาย East:
-- sql/update-from-fixed.sqlBEGIN;UPDATE books bSET copies_sold = e.totalFROM (SELECT book_id, sum(copies) AS total FROM sales WHERE region = 'East' GROUP BY book_id) eWHERE e.book_id = b.idRETURNING b.id, b.title, old.copies_sold AS was, new.copies_sold AS now;ROLLBACK;BEGIN id | title | was | now----+-------------------+------+----- 1 | Quiet Harbors | 450 | 50 2 | The Glass Orchard | 1250 | 900 3 | Tidal Notes | 600 | 200(3 rows)
UPDATE 3ROLLBACKRETURNING old.x, new.x คือสิ่งที่ทำให้ bug ชัด ใช้มัน (หรือ SELECT count(*) นำหน้า) กับทุกการแก้ข้อมูล
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”UPDATE และ DELETE คือ SELECT บวก action: WHERE (และ join FROM/USING) เลือกเซตของ row และ action ใช้กับเซตนั้น ถ้าเซตผิด action ก็ผิดแบบเงียบ ๆ ทุก workflow ที่ปลอดภัยจึงเกี่ยวกับการตรวจเซตก่อนลงมือ: SELECT count(*) ก่อน RETURNING หลัง และครอบด้วย transaction ภายใน MVCC หมายความว่าทั้งสอง statement ไม่ทำในที่เดิม ทั้งคู่สร้าง row เวอร์ชันตายให้ vacuum เก็บภายหลัง
เคล็ดลับและข้อควรระวัง
หัวข้อที่มีชื่อว่า “เคล็ดลับและข้อควรระวัง”- ใส่ clause
WHEREให้UPDATEและDELETEเสมอ เว้นแต่คุณตั้งใจแตะทุก row จริง ๆWHEREที่ลืมเขียนทับหรือทำให้ทั้ง table ว่าง - ดูตัวอย่าง statement ที่ทำลายล้างก่อนด้วย
SELECT count(*) ... WHERE ...ที่ตรงกัน เพื่อยืนยันว่าคุณกำลังจะกระทบกี่ row - ครอบการเปลี่ยนแปลงที่เสี่ยงหรือหลายขั้นด้วย transaction เพื่อ
ROLLBACKได้ถ้าผลผิด - ใช้
RETURNING(พร้อมold/newใน PostgreSQL 18) เพื่อตรวจว่า row ไหนเปลี่ยนแน่ ๆ แทนที่จะเชื่อจำนวน row อย่างเดียว - ใน
UPDATE ... FROMและDELETE ... USINGใส่เงื่อนไข join ในWHEREเสมอ DELETEอาจล้มเหลวถ้า table อื่นอ้างอิง row ผ่าน foreign key (23503)ON DELETE CASCADEเปลี่ยนสิ่งนั้น ด้วยราคาคือการลบที่ใหญ่ขึ้น- ใช้
TRUNCATE(ไม่ใช่DELETE) เพื่อล้างทั้ง table
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
UPDATE/DELETE พร้อม WHERE เฉพาะ | ปลอดภัย แตะเฉพาะ row ที่ตั้งใจ | ต้องทำเงื่อนไขให้ถูกต้องสมบูรณ์ก่อนรัน |
UPDATE/DELETE ที่ไม่มี WHERE | เขียนเร็ว ไม่ต้องคิดเงื่อนไข | โดนทุก row ทันที — หายนะถ้ารันผิด table หรือผิดสภาพแวดล้อม |
ครอบด้วย transaction ที่ ROLLBACK ได้ | ทางออกฉุกเฉินก่อน COMMIT จริง | ต้องจำเปิด/ปิด transaction และตรวจผลทุกครั้ง |
| รันตรง ๆ นอก transaction | เร็ว ไม่ต้องจัดการ transaction | ไม่มีปุ่มยกเลิกถ้าผลผิด |
TRUNCATE แทน DELETE FROM | ทันที ไม่มีงานต่อ row | ต้องใช้ lock แรง ถูกปฏิเสธเมื่อมี foreign key เว้นแต่ CASCADE |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “
DELETEคืนพื้นที่ดิสก์” มันแค่ทำเครื่องหมาย row ว่าตาย มีแต่VACUUMที่ทำให้พื้นที่ใช้ซ้ำได้ และมีเพียง page ว่างท้ายไฟล์ที่ถูกคืนให้ OS (heap ข้างบนไม่เล็กลง) - “UPDATE เปลี่ยน row ในที่เดิม” มันเขียนเวอร์ชันใหม่ (heap ใหญ่ขึ้นเท่าตัวหลัง update ทั้ง table หนึ่งครั้ง)
- “
UPDATE ... FROMที่ไม่มี join เป็น syntax error” เป็น SQL ที่ถูกต้องและ update ทุก row ด้วยผลจับคู่ตามอำเภอใจ (พิสูจน์ข้างบน) - “จำนวน row พอจะรู้ว่าสำเร็จ”
UPDATE 4ดูปกติจนกว่าคุณคาดหวัง 1 ส่วนRETURNINGแสดงว่า row ไหน - “
TRUNCATErollback ไม่ได้” ใน PostgreSQL ทำได้ ภายใน transaction