ทำไม Postgres UPDATE ใช้เวลา 39 ชั่วโมง


17

ฉันมีตาราง Postgres ที่มีแถวประมาณ 2.1 ล้านแถว ฉันวิ่งไปตามการอัพเดทด้านล่าง:

WITH stops AS (
    SELECT id,
           rank() OVER (ORDER BY offense_timestamp,
                     defendant_dl,
                     offense_street_number,
                     offense_street_name) AS stop
    FROM   consistent.master
    WHERE  citing_jurisdiction=1
)

UPDATE consistent.master
SET arrest_id=stops.stop
FROM stops
WHERE master.id = stops.id;

แบบสอบถามนี้ใช้เวลา 39 ชั่วโมงในการทำงาน ฉันใช้สิ่งนี้กับโปรเซสเซอร์แล็ปท็อป Core i7 Q720 4 ตัว (จริง) RAM จำนวนมากไม่มีอะไรที่ใช้เวลาส่วนใหญ่อีกต่อไป ไม่มีข้อ จำกัด พื้นที่ HDD ตารางเพิ่งถูกดูดวิเคราะห์และทำดัชนีใหม่

ตลอดเวลาที่คิวรีรันอยู่อย่างน้อยที่สุดหลังจากการทำครั้งแรกWITHเสร็จสิ้นการใช้งาน CPU มักจะต่ำและ HDD นั้นใช้งาน 100% ฮาร์ดดิสถูกใช้งานอย่างหนักจนแอพอื่นวิ่งช้ากว่าปกติมาก

การตั้งค่าพลังงานของแล็ปท็อปมีประสิทธิภาพสูง (Windows 7 x64)

นี่คือคำอธิบาย:

Update on master  (cost=822243.22..1021456.89 rows=2060910 width=312)
  CTE stops
    ->  WindowAgg  (cost=529826.95..581349.70 rows=2060910 width=33)
          ->  Sort  (cost=529826.95..534979.23 rows=2060910 width=33)
                Sort Key: consistent.master.offense_timestamp, consistent.master.defendant_dl, consistent.master.offense_street_number, consistent.master.offense_street_name
                ->  Seq Scan on master  (cost=0.00..144630.06 rows=2060910 width=33)
                      Filter: (citing_jurisdiction = 1)
  ->  Hash Join  (cost=240893.51..440107.19 rows=2060910 width=312)
        Hash Cond: (stops.id = consistent.master.id)
        ->  CTE Scan on stops  (cost=0.00..41218.20 rows=2060910 width=48)
        ->  Hash  (cost=139413.45..139413.45 rows=2086645 width=268)
              ->  Seq Scan on master  (cost=0.00..139413.45 rows=2086645 width=268)

citing_jurisdiction=1แยกออกจากแถวไม่กี่หมื่นเท่านั้น แม้จะมีWHEREข้อนั้นฉันยังคงทำงานมากกว่า 2 ล้านแถว

ฮาร์ดไดรฟ์นั้นเข้ารหัสด้วยไดรฟ์ทั้ง TrueCrypt 7.1a ว่าสิ่งที่ช้าลงเล็กน้อย แต่ไม่มากพอที่จะทำให้เกิดแบบสอบถามเพื่อใช้เวลาที่หลายชั่วโมง

WITHส่วนหนึ่งจะใช้เวลาเพียงประมาณ 3 นาทีในการทำงาน

arrest_idฟิลด์มีดัชนีที่สำคัญต่างประเทศไม่มี มีดัชนี 8 ตัวและกุญแจต่างประเทศ 2 ตัวในตารางนี้ เขตข้อมูลอื่น ๆ ทั้งหมดในแบบสอบถามจะถูกทำดัชนี

arrest_idฟิลด์ไม่มีข้อ จำกัด NOT NULLยกเว้น

ตารางมีทั้งหมด 32 คอลัมน์

arrest_idเป็นประเภทตัวอักษรแม่เหล็ก (20) ฉันรู้ว่าrank()สร้างค่าตัวเลข แต่ฉันต้องใช้ตัวอักษรที่แตกต่างกัน (20)เพราะฉันมีแถวอื่น ๆ ที่citing_jurisdiction<>1ใช้ข้อมูลที่ไม่ใช่ตัวเลขสำหรับเขตข้อมูลนี้

ฟิลด์เป็นที่ว่างเปล่าสำหรับแถวทั้งหมดที่มีarrest_idciting_jurisdiction=1

นี่เป็นแล็ปท็อปส่วนบุคคลระดับไฮเอนด์ (ณ 1 ปีที่ผ่านมา) ฉันเป็นผู้ใช้คนเดียว ไม่มีการสอบถามหรือการดำเนินการอื่น ๆ ที่กำลังทำงานอยู่ การล็อคดูเหมือนไม่น่าเป็นไปได้

ไม่มีทริกเกอร์ใด ๆ ในตารางนี้หรือที่อื่นใดในฐานข้อมูล

การดำเนินการอื่น ๆ ในฐานข้อมูลนี้จะไม่ใช้เวลานานเกินไป ด้วยการจัดทำดัชนีที่เหมาะสมSELECTแบบสอบถามมักจะค่อนข้างเร็ว


ผู้ที่Seq Scanเป็นบิตที่น่ากลัว ...
rogerdpack

คำตอบ:


18

ฉันมีสิ่งที่คล้ายกันเกิดขึ้นเมื่อเร็ว ๆ นี้ด้วยตาราง 3.5 ล้านแถว การอัปเดตของฉันจะไม่จบ หลังจากการทดลองและความหงุดหงิดมากมายในที่สุดฉันก็พบผู้กระทำผิด มันกลายเป็นดัชนีบนโต๊ะที่กำลังอัพเดท

วิธีแก้ไขคือลบดัชนีทั้งหมดบนตารางที่กำลังอัพเดตก่อนรันคำสั่ง update เมื่อฉันทำอย่างนั้นการอัปเดตจะเสร็จสิ้นในไม่กี่นาที เมื่อการอัปเดตเสร็จสมบูรณ์ฉันจะสร้างดัชนีขึ้นใหม่และกลับมาทำธุรกิจอีกครั้ง นี่อาจจะไม่ช่วยคุณ ณ จุดนี้ แต่คนอื่นอาจหาคำตอบ

ฉันจะเก็บดัชนีไว้ในตารางที่คุณกำลังดึงข้อมูล คุณไม่จำเป็นต้องอัปเดตดัชนีใด ๆ และควรช่วยในการค้นหาข้อมูลที่คุณต้องการอัปเดต มันทำงานได้ดีบนแล็ปท็อปที่ช้า


3
ฉันเปลี่ยนคำตอบที่ดีที่สุดสำหรับคุณ ตั้งแต่ฉันโพสต์สิ่งนี้ฉันได้พบกับสถานการณ์อื่น ๆ ที่ดัชนีมีปัญหาแม้ว่าคอลัมน์ที่อัปเดตมีค่าอยู่แล้วและไม่มีดัชนี (!) ดูเหมือนว่า Postgres มีปัญหากับวิธีจัดการดัชนีในคอลัมน์อื่น ไม่มีเหตุผลใดที่ดัชนีอื่น ๆ เหล่านี้จะบอลลูนเวลาสอบถามของการอัปเดตเมื่อการเปลี่ยนแปลงเพียงอย่างเดียวในตารางคือการปรับปรุงคอลัมน์ที่ไม่ได้จัดทำดัชนีและคุณไม่ได้เพิ่มพื้นที่ที่จัดสรรสำหรับแถวของคอลัมน์นั้น
Aren Cambre

1
ขอบคุณ! หวังว่ามันจะช่วยให้ผู้อื่น มันจะช่วยให้ฉันปวดหัวหลายชั่วโมงสำหรับบางสิ่งที่ดูเหมือนง่ายมาก
JC Avena

5
@ArenCambre - มีเหตุผล: PostgreSQL คัดลอกทั้งแถวไปยังตำแหน่งอื่นและทำเครื่องหมายรุ่นเก่าว่าถูกลบ นี่คือวิธีที่ PostgreSQL ดำเนินการควบคุมพร้อมกันหลายรุ่น (MVCC)
Piotr Findeisen

คำถามของฉันคือ ... ทำไมจึงเป็นผู้ร้าย ดูstackoverflow.com/a/35660593/32453 ด้วย
rogerdpack

15

ปัญหาที่ใหญ่ที่สุดของคุณคือการทำงานหนักในการเขียนจำนวนมากการค้นหาอย่างหนักบนฮาร์ดไดรฟ์แล็ปท็อป ไม่เคยเร็วขนาดนี้ไม่ว่าคุณจะทำอะไรโดยเฉพาะอย่างยิ่งถ้ามันเป็นไดรฟ์ 5400RPM ที่ช้ากว่าที่ส่งมาในแล็ปท็อปจำนวนมาก

TrueCrypt ทำให้ช้าลงมากกว่า "a bit" สำหรับการเขียน การอ่านจะเร็วพอสมควร แต่การเขียนทำให้ RAID 5 ดูรวดเร็ว การเรียกใช้ฐานข้อมูลบนปริมาณ TrueCrypt จะเป็นการทรมานสำหรับการเขียนโดยเฉพาะการเขียนแบบสุ่ม

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

BEGIN;
SELECT ... INTO TEMPORARY TABLE master_tmp ;
TRUNCATE TABLE consistent.master;
-- Now DROP all constraints on consistent.master, then:
INSERT INTO consistent.master SELECT * FROM master_tmp;
-- ... and re-create any constraints.

ฉันสงสัยว่าจะเร็วกว่าการปล่อยและสร้างข้อ จำกัด เพียงอย่างเดียวอีกครั้งเนื่องจากการอัปเดตจะมีรูปแบบการเขียนที่ค่อนข้างสุ่มซึ่งจะฆ่าที่เก็บข้อมูลของคุณ เม็ดมีดจำนวนมากสองอันหนึ่งเม็ดลงในตารางที่ไม่ถูกบล็อกและอีกหนึ่งเม็ดในตาราง WAL-log ที่ไม่มีข้อ จำกัด น่าจะเร็วกว่า

หากคุณมีการสำรองข้อมูลที่ทันสมัยอย่างแน่นอนและไม่จำเป็นต้องกู้คืนฐานข้อมูลของคุณจากการสำรองข้อมูลคุณสามารถเริ่ม PostgreSQL อีกครั้งด้วยfsync=offพารามิเตอร์และfull_page_writes=off ชั่วคราวสำหรับการดำเนินการเป็นกลุ่มนี้ ปัญหาที่ไม่คาดคิดใด ๆ fsync=offเช่นการสูญเสียพลังงานหรือความผิดพลาดของระบบปฏิบัติการจะออกจากฐานข้อมูลในขณะที่ไม่สามารถกู้คืนของคุณ

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

เช่นเดียวกับใน Oracle เป็นความคิดที่ดีที่จะปล่อยดัชนีแล้วสร้างใหม่อีกครั้งหลังจากการอัปเดตชุดใหญ่ ผู้วางแผนของ PostgreSQL ไม่สามารถทราบได้ว่ามีการอัพเดทครั้งใหญ่หยุดการอัปเดตดัชนีชั่วคราวแล้วสร้างดัชนีใหม่ในตอนท้าย แม้ว่ามันจะทำได้ แต่มันก็ยากที่จะเข้าใจว่าประเด็นนี้คุ้มค่าที่จะทำโดยเฉพาะล่วงหน้า


คำตอบนี้เป็นจุดที่จำนวนมากของการเขียนและการเข้ารหัสที่น่ากลัวบวกกับไดรฟ์แล็ปท็อปช้า ฉันยังอยากจะทราบว่าการปรากฏตัวของ 8 ดัชนีผลิตเขียนพิเศษมากมายและการบังคับใช้ของเอาชนะHOTในบล็อกการปรับปรุงแถวเพื่อวางดัชนีและการใช้ที่ต่ำกว่าfillfactorบนโต๊ะอาจป้องกันไม่ให้ตันของการย้ายถิ่นแถว
dbenhur

1
การเรียกร้องให้เพิ่มโอกาส HOTs ด้วยการเติมสารเติมแต่ง - ด้วย TrueCrypt บังคับให้บล็อกอ่าน - เขียนซ้ำในบล็อกขนาดใหญ่ฉันไม่แน่ใจว่ามันจะช่วยได้มาก การโยกย้ายแถวอาจทำได้เร็วกว่าเพราะการเติบโตของตารางอย่างน้อยก็ทำบล็อกการเขียนเชิงเส้น
Craig Ringer

2.5 ปีต่อมาฉันกำลังทำอะไรที่คล้ายกัน แต่อยู่บนโต๊ะที่ใหญ่กว่า เพียงเพื่อให้แน่ใจว่าเป็นความคิดที่ดีหรือไม่ที่จะทำดัชนีทั้งหมดแม้ว่าคอลัมน์เดียวที่ฉันอัปเดตจะไม่ได้รับการจัดทำดัชนี?
Aren Cambre

1
@ArenCambre ในกรณีนี้ ... มันก็ซับซ้อน หากการอัปเดตส่วนใหญ่ของคุณจะมีสิทธิ์HOTคุณควรปล่อยให้ดัชนีอยู่กับที่ ถ้าไม่เช่นนั้นคุณอาจต้องการวางและสร้างใหม่อีกครั้ง คอลัมน์ไม่ได้ถูกจัดทำดัชนี แต่เพื่อให้สามารถทำการอัปเดตที่ร้อนแรงได้นั้นจำเป็นต้องมีพื้นที่ว่างในหน้าเดียวกันดังนั้นจึงขึ้นอยู่กับจำนวนเนื้อที่ที่เหลือในตาราง หากเป็นส่วนใหญ่เขียนฉันจะบอกว่าวางดัชนีทั้งหมด หากมีการอัปเดตล็อตอาจมีรูและคุณอาจตกลง เครื่องมือที่ชอบpageinspectและpg_freespacemapสามารถช่วยพิจารณาสิ่งนี้
Craig Ringer

ขอบคุณ ในกรณีนี้มันเป็นคอลัมน์บูลีนที่มีรายการในทุกแถวอยู่แล้ว ฉันกำลังเปลี่ยนรายการในบางแถว ฉันเพิ่งยืนยันว่า: การอัปเดตใช้เวลาเพียง 2 ชั่วโมงหลังจากวางดัชนีทั้งหมด ก่อนหน้านี้ฉันต้องหยุดการอัปเดตหลังจาก 18 ชั่วโมงเพราะใช้เวลานานเกินไป นี่คือความจริงที่ว่าคอลัมน์ที่มีการปรับปรุงแน่นอนไม่ได้จัดทำดัชนี
Aren Cambre

2

บางคนจะให้คำตอบที่ดีกว่าสำหรับ Postgres แต่นี่เป็นข้อสังเกตเล็กน้อยจากมุมมองของ Oracle ที่อาจนำไปใช้ (และความคิดเห็นยาวเกินไปสำหรับฟิลด์ข้อคิดเห็น)

ข้อกังวลแรกของฉันคือพยายามอัปเดต 2 ล้านแถวในหนึ่งธุรกรรม ใน Oracle คุณจะต้องเขียนรูปก่อนของแต่ละบล็อกที่มีการอัปเดตเพื่อให้เซสชันอื่น ๆ ยังคงมีการอ่านที่สอดคล้องกันโดยไม่ต้องอ่านบล็อกที่ปรับเปลี่ยนของคุณและคุณมีความสามารถในการย้อนกลับ นั่นคือการย้อนกลับที่ยาวนานซึ่งถูกสร้างขึ้น โดยปกติคุณจะดีกว่าที่จะทำธุรกรรมเป็นชิ้นเล็ก ๆ พูด 1,000 รายการในแต่ละครั้ง

หากคุณมีดัชนีอยู่บนโต๊ะและตารางจะถูกพิจารณาว่าไม่ทำงานในระหว่างการบำรุงรักษาคุณมักจะดีกว่าที่จะลบดัชนีออกก่อนที่จะมีการดำเนินการครั้งใหญ่และจากนั้นสร้างใหม่อีกครั้งในภายหลัง ราคาถูกกว่านั้นพยายามรักษาดัชนีอย่างต่อเนื่องด้วยการอัพเดทแต่ละครั้ง

Oracle อนุญาตให้คำแนะนำ "ไม่มีการบันทึก" ในคำสั่งเพื่อหยุดการบันทึกรายวัน มันเพิ่มความเร็วในงบมาก แต่ปล่อย db ของคุณในสถานการณ์ "ไม่สามารถกู้คืนได้" ดังนั้นคุณต้องสำรองข้อมูลก่อนและสำรองอีกครั้งในภายหลัง ฉันไม่รู้ว่า Postgres มีตัวเลือกที่คล้ายกันหรือไม่


PostgreSQL ไม่มีปัญหากับการย้อนกลับที่ยาวนานไม่มีอยู่ ย้อนกลับอย่างรวดเร็วใน PostgreSQL ไม่ว่าธุรกรรมของคุณจะใหญ่แค่ไหน Oracle! = PostgreSQL
Frank Heikens

@ FrankHeikens ขอบคุณที่น่าสนใจ ฉันจะต้องอ่านเกี่ยวกับการทำงานของ journalling ใน Postgres เพื่อที่จะทำให้แนวคิดทั้งหมดของธุรกรรมทำงานได้ต้องมีการปรับปรุงข้อมูลสองเวอร์ชันที่ต่างกันในระหว่างการทำธุรกรรมภาพก่อนหน้าและภาพหลังซึ่งเป็นกลไกที่ฉันใช้อ้างอิง ไม่ทางใดก็ทางหนึ่งฉันเดาว่ามีขีด จำกัด เกินกว่าที่ทรัพยากรในการรักษาธุรกรรมจะแพงเกินไป
เกล็นมี. ค.

2
postgres @Glenn ช่วยให้รุ่นของแถวในตารางของตัวเอง - ดูที่นี่สำหรับคำอธิบาย การประนีประนอมคือคุณจะได้รับ 'tuples' ที่แขวนอยู่รอบ ๆ ซึ่งถูกทำความสะอาดแบบอะซิงโครนัสกับสิ่งที่เรียกว่า 'สูญญากาศ' ใน postgres (Oracle ไม่ต้องการสูญญากาศเพราะไม่เคยมีแถว 'ตาย' ในตาราง)
Jack ลอง topanswers.xyz

คุณยินดีและค่อนข้างล่าช้า: ยินดีต้อนรับสู่เว็บไซต์ :-)
แจ็คพูดว่าลอง topanswers.xyz

@Glenn เอกสาร canonical สำหรับการควบคุมการทำงานพร้อมกันของแถวรุ่นPostgreSQLคือpostgresql.org/docs/current/static/mvcc-intro.htmlและคุ้มค่ากับการอ่าน ดูเพิ่มเติมwiki.postgresql.org/wiki/MVCC โปรดทราบว่า MVCC ที่มีแถวที่ตายแล้วและVACUUMเป็นเพียงครึ่งเดียวของคำตอบ PostgreSQL ยังใช้สิ่งที่เรียกว่า "บันทึกล่วงหน้าเขียน" (บันทึกประจำวันได้อย่างมีประสิทธิภาพ) เพื่อจัดเตรียมอะตอมและป้องกันการเขียนบางส่วน ฯลฯ ดูpostgresql.org/docs/current/static/wal-intro.html
Craig Ringer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.