Postgres UPDATE neden 39 saat sürdü?


17

~ 2.1 milyon sıralı bir Postgres masam var. Üzerinde aşağıdaki güncellemeyi çalıştırdım:

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;

Bu sorgunun çalışması 39 saat sürdü. Ben 4 (fiziksel) core i7 Q720 dizüstü bilgisayar işlemcisi, bol RAM, zamanın büyük çoğunluğunu çalıştıran başka bir şey üzerinde çalışıyorum. HDD alan kısıtlaması yok. Masa yakın zamanda vakumlanmış, analiz edilmiş ve yeniden endekslenmiştir.

Sorgunun çalıştığı süre boyunca, en azından ilk WITHtamamlandıktan sonra , CPU kullanımı genellikle düşüktü ve HDD% 100 kullanımdaydı. HDD o kadar sert kullanılıyordu ki diğer uygulamalar normalden çok daha yavaş çalışıyordu.

Dizüstü bilgisayarın güç ayarı Yüksek performanstaydı (Windows 7 x64).

İşte EXPLAIN:

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=1yalnızca on binlerce satırı hariç tutar. Bu WHEREmaddede bile , hala 2 milyondan fazla satır üzerinde çalışıyorum.

Sabit sürücü TrueCrypt 7.1a ile tümüyle şifrelenmiştir. Yeterince Yani yavaşlatır biraz aşağı şeyler ama bir sorgu almaya neden olduğu uzun saatler.

WITHKısmı sadece çalıştırmak için 3 dakika sürer.

arrest_idAlan yabancı anahtar için hiçbir indeksi vardı. Bu tabloda 8 dizin ve 2 yabancı anahtar vardır. Sorgudaki diğer tüm alanlar dizine eklenir.

arrest_idAlan dışında herhangi bir sınırlama olmaması NOT NULL.

Tabloda toplam 32 sütun vardır.

arrest_idkarakter tipindedir (20) . Ben rank()sayısal bir değer üretir, ancak bu alan için sayısal olmayan veri kullanan başka satırları var çünkü ben karakter değişen (20) kullanmak zorunda farkında citing_jurisdiction<>1.

arrest_idAlan ile tüm satırlar için boş oldu citing_jurisdiction=1.

Bu kişisel, üst seviye (1 yıl önce) bir dizüstü bilgisayar. Tek kullanıcı benim. Başka sorgu veya işlem yürütülmüyordu. Kilitleme pek olası görünmüyor.

Bu tablonun herhangi bir yerinde veya veritabanında başka hiçbir yerde tetikleyici yoktur.

Bu veritabanındaki diğer işlemler hiçbir zaman anormal bir zaman almaz. Doğru indeksleme ile SELECTsorgular genellikle oldukça hızlıdır.


Bunlar Seq Scanbiraz korkutucu ...
rogerdpack

Yanıtlar:


18

Son zamanlarda 3,5 milyon sıralık bir tablo ile benzer bir şey yaşadım. Güncellemem hiç bitmeyecek. Bir sürü deneyden ve hayal kırıklığından sonra, suçluyu buldum. Güncellenen tabloda dizinler olduğu ortaya çıktı.

Çözüm, update deyimini çalıştırmadan önce güncellenen tablodaki tüm dizinleri bırakmaktı. Bunu yaptıktan sonra güncelleme birkaç dakika içinde sona erdi. Güncelleme tamamlandığında, dizinleri yeniden oluşturdum ve işime geri döndüm. Bu muhtemelen bu noktada size yardımcı olmayacaktır, ancak cevap arayan başka biri olabilir.

Verileri çektiğiniz tablodaki indeksleri tutardım. Bu, herhangi bir dizini güncellemeye devam etmek zorunda kalmaz ve güncellemek istediğiniz verileri bulmanıza yardımcı olur. Yavaş bir dizüstü bilgisayarda iyi koştu.


3
Size en iyi yanıtı değiştiriyorum. Bunu yayınladığımdan beri, güncellenen sütunun zaten bir değeri ve hiçbir dizini (!) Olmasa bile, dizinlerin sorun olduğu diğer durumlarla karşılaştım. Postgres'in diğer sütunlardaki dizinleri yönetme konusunda bir sorunu var gibi görünüyor. Bu diğer dizinlerin, bir tablodaki tek değişiklik dizini kaldırılmamış bir sütunu güncellemek olduğunda ve bu sütunun herhangi bir satırı için ayrılan alanı artırmamanız durumunda, güncellemenin sorgu süresini balonlamak için bir neden yoktur.
Aren Cambre

1
Teşekkürler! Umarım başkalarına yardımcı olur. Çok basit görünen bir şey için saatler boyu başım ağrıyor.
JC Avena

5
@ArenCambre - bir nedeni var: PostgreSQL tüm satırı farklı bir konuma kopyalar ve eski sürümü silinmiş olarak işaretler. PostgreSQL, Çoklu Sürüm Eşzamanlılık Kontrolünü (MVCC) bu şekilde uygular.
Piotr Findeisen

Sorum şu ... neden suçlu? Ayrıca bkz. Stackoverflow.com/a/35660593/32453
rogerdpack

15

En büyük sorununuz bir dizüstü bilgisayar sabit diskinde çok miktarda yazma ağır, ağır arama yapmaktır. Ne yaparsanız yapın bu asla hızlı olmayacaktır, özellikle de birçok dizüstü bilgisayarda gönderilen daha yavaş 5400 RPM sürücü ise.

TrueCrypt yazma işlemi için işleri "biraz" dan daha fazla yavaşlatır. Okumalar oldukça hızlı olacaktır, ancak yazma işlemleri RAID 5'in hızlı görünmesini sağlar. TrueCrypt biriminde DB çalıştırmak yazma işlemleri, özellikle rasgele yazma işlemleri için işkence görür.

Bu durumda, sorguyu optimize etmeye çalışırken zamanınızı boşa harcayacağınızı düşünüyorum. Yine de çoğu satırı yeniden yazıyorsunuz ve korkunç yazma durumunuzla yavaş olacak . Ne tavsiye ederim:

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.

Bunun sadece kısıtlamaları bırakmak ve yeniden oluşturmaktan daha hızlı olacağından şüpheleniyorum, çünkü bir UPDATE depolamanızı öldürecek oldukça rastgele yazma kalıplarına sahip olacak . Biri bloke edilmemiş bir tabloya ve bir tanesi kısıtlama olmaksızın WAL günlüğe kaydedilmiş bir tabloya iki toplu ekleme muhtemelen daha hızlı olacaktır.

Kesinlikle güncel yedeklemeleriniz varsa ve veritabanınızı yedeklemelerden geri yüklemek zorunda kalmazsanız PostgreSQL'i fsync=offparametreyle ve full_page_writes=off geçici olarak bu toplu işlem için yeniden başlatabilirsiniz . Güç kaybı veya işletim sistemi çökmesi gibi beklenmedik sorunlar veritabanınızı kurtarılamaz duruma getirecektir fsync=off.

"Günlüğe kaydetme yok" ifadesine eşdeğer POSTGreSQL, bloke edilmemiş tabloları kullanmaktır. DB kirliyken kirli bir şekilde kapanırsa bu tıkanmamış tablolar kesilir. Bunlar olabilir yani en azından senin yazma yükünü yarıya ve arayan sayısını azaltacaktır unlogged tabloları kullanarak LOT daha hızlı.

Oracle'da olduğu gibi, büyük bir toplu güncellemeden sonra bir dizini bırakmak ve yeniden oluşturmak iyi bir fikir olabilir. PostgreSQL'in planlayıcısı büyük bir güncellemenin yapıldığını anlayamaz, dizin güncellemelerini duraklatamaz ve sonunda dizini yeniden oluşturamaz; yapabilseydi bile, bunun hangi noktada, özellikle de önceden yapmaya değer olduğunu anlaması çok zor olurdu.


Bu cevap çok sayıda yazıda ve korkunç şifreleme artı yavaş dizüstü bilgisayar sürücüsünde nokta. Ben de 8 indeks varlığının birçok ekstra yazma ve yenilgilere uygulanabilirliği üretir dikkat ediyorum SICAK böylece endeksler bırakarak ve bir alt kullanarak, içinde blok satır güncellemeleri fillfactor satır göç bir ton önleyebilir masaya
dbenhur

1
Bir dolgu faktörü ile HOTs şansını artırma konusunda iyi çağrı - TrueCrypt zor bloklarda okuma-yeniden yazma döngülerini zorlayan olsa da çok yardımcı olacağından emin değilim; Satır göçü daha da hızlı olabilir çünkü tabloyu büyütmek en azından lineer-ish yazma bloklarını yapıyor.
Craig Ringer

2.5 yıl sonra benzer bir şey yapıyorum ama daha büyük bir masada. Sadece emin olmak için, güncellediğim tek sütun dizine eklenmemiş olsa bile, tüm dizinleri bırakmak iyi bir fikir mi?
Aren Cambre

1
@ArenCambre Bu durumda ... iyi, karmaşık. Güncellemelerinizin çoğu uygunsa HOT, dizinleri yerinde bırakmak daha iyidir. Değilse, büyük olasılıkla düşüp yeniden oluşturmak istersiniz. Sütun endekslenmemiştir, ancak SICAK bir güncelleme yapabilmek için aynı sayfada boş alan olması gerekir, bu nedenle tabloda ne kadar ölü alan olduğuna biraz bağlıdır. Çoğunlukla yazma ise, tüm dizinleri bırak diyebilirim. Güncellenmiş bir sürü ise delik olabilir ve iyi olabilir. Gibi pageinspectve böyle araçlar pg_freespacemapbunu belirlemeye yardımcı olabilir.
Craig Ringer

Teşekkürler. Bu durumda, zaten her satıra bir girişi olan bir boole sütunu. Bazı satırlardaki girişi değiştiriyordum. Az önce onayladım: güncelleme tüm dizinleri bıraktıktan sonra sadece 2 saat sürdü. Önceden, 18 saat sonra güncellemeyi durdurmak zorunda kaldım çünkü çok uzun sürüyordu. Bu, güncellenmekte olan sütunun kesinlikle dizine eklenmemiş olmasına rağmen.
Aren Cambre

2

Birisi Postgres için daha iyi bir cevap verecektir, ancak burada geçerli olabilecek bir Oracle perspektifinden birkaç gözlem var (ve yorumlar yorum alanı için çok uzun).

İlk endişem, bir işlemde 2 milyon satırı güncellemeye çalışmak olacaktır. Oracle'da, güncellenen her bloğun bir önceki görüntüsünü yazıyorsunuz, böylece diğer oturumlar değiştirilmiş bloklarınızı okumadan tutarlı bir şekilde okunabilir ve geri dönme olanağınız olur. Bu uzun bir geri dönüş inşa ediliyor. İşlemleri genellikle küçük parçalar halinde yapmak daha iyidir. Bir seferde 1.000 kayıt söyleyin.

Tabloda dizinleriniz varsa ve bakım sırasında tablonun çalışma dışı olduğu düşünülüyorsa, büyük bir işlemden önce dizinleri kaldırmak ve daha sonra yeniden oluşturmak için genellikle daha iyi durumda olursunuz. Daha sonra her güncellenen kayıtla sürekli olarak dizinleri korumaya çalışıyoruz.

Oracle, günlüklendirmeyi durdurmak için ifadeler üzerinde "günlüğe kaydetme" ipuçlarına izin verir. Bu ifadeleri çok hızlandırır, ancak db'nizi "kurtarılamaz" durumda bırakır. Böylece daha önce yedeklemek ve hemen sonra tekrar yedeklemek istersiniz. Postgres'in benzer seçenekleri olup olmadığını bilmiyorum.


PostgreSQL'in uzun bir geri alma ile ilgili sorunları yoktur, mevcut değildir. İşleminiz ne kadar büyük olursa olsun, ROLBACK PostgreSQL'de çok hızlıdır. Oracle! = PostgreSQL
Frank Heikens

@FrankHeikens Teşekkürler, bu ilginç. Postgres'de dergilerin nasıl çalıştığını okumalıyım. Tüm işlem kavramının çalışması için, bir işlem sırasında, önceki görüntü ve sonraki görüntü gibi bir şekilde verilerin iki farklı sürümünün muhafaza edilmesi gerekir ve bahsettiğim mekanizma budur. Öyle ya da böyle, işlemi sürdürmek için kaynakların çok pahalı olacağı bir eşik var sanırım.
Glenn

2
@Glenn postgres, satırın sürümlerini tablonun kendisinde tutar - açıklama için buraya bakın . Uzlaşma, postgreslerde 'vakum' olarak adlandırılan şeyle eşzamansız olarak temizlenen 'ölü' tüpleri almanızdır (Oracle'ın vakumda ihtiyacı yoktur çünkü masanın kendisinde asla 'ölü' satır yoktur)
Jack diyor deneyin topanswers.xyz

Hoş geldiniz ve oldukça geç kaldım: siteye hoşgeldiniz :-)
Jack diyor topanswers.xyz diyor

@Glenn PostgreSQL'in satır sürümü eşzamanlılık kontrolü için standart belge postgresql.org/docs/current/static/mvcc-intro.html ve okunmaya değer. Ayrıca bkz . Wiki.postgresql.org/wiki/MVCC . Ölü satırlara sahip MVCC VACUUMve cevabın sadece yarısı olduğunu unutmayın ; PostgreSQL ayrıca, atomik taahhütler sağlamak ve kısmi yazmalara vb. Karşı koruma sağlamak için "önceden yazma günlüğü" (etkili bir günlük) kullanır. Bkz. Postgresql.org/docs/current/static/wal-intro.html
Craig Ringer
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.