Değişmeyen sütunlar bile tüm sütunları güncellemenin yükü nedir?


17

Bir satırın güncellenmesi söz konusu olduğunda, birçok ORM aracı , söz konusu varlıkla ilişkilendirilmiş her sütunu ayarlayan bir UPDATE ifadesi yayınlar .

Bunun avantajı, UPDATEhangi varlık özniteliğini değiştirdiğinizden bağımsız olarak ifade aynı olduğundan güncelleme raporlarını kolayca toplu olarak işleyebilmenizdir . Dahası, sunucu tarafı ve istemci tarafı deyimi önbelleğe almayı da kullanabilirsiniz.

Dolayısıyla, bir varlığı yükler ve yalnızca tek bir özellik ayarlarsam:

Post post = entityManager.find(Post.class, 1L);
post.setScore(12);

Tüm sütunlar değiştirilecek:

UPDATE post
SET    score = 12,
       title = 'High-Performance Java Persistence'
WHERE  id = 1

Şimdi, titlemülk üzerinde de bir dizinimiz olduğu varsayılarak , DB değerin yine de değişmediğini fark etmemeli mi?

Gelen bu makalede , Markus Winand diyor ki:

Tüm sütunlardaki güncelleme, önceki bölümlerde daha önce gözlemlediğimiz kalıbı göstermektedir: yanıt süresi her ek dizinle birlikte büyür.

Veritabanı ilişkili veri sayfasını diskten belleğe yüklediğinden ve bu nedenle bir sütun değerinin değiştirilmesi gerekip gerekmediğini anlayabildiğinden neden bu yükü merak ediyorum.

Dizinler için bile, hiçbir şey yeniden dengelenmez, çünkü dizin değerleri değişmemiş sütunlar için değişmez, ancak bunlar GÜNCELLEME'ye dahil edilir.

Gereksiz değişmeyen sütunlarla ilişkili B + Ağacı dizinlerinde de gezinilmesi gerekiyor mu, yalnızca veritabanının yaprak değerinin hala aynı olduğunu fark etmesi için mi?

Tabii ki, bazı ORM araçları sadece değişen özellikleri GÜNCELLEMEK için izin verir:

UPDATE post
SET    score = 12,
WHERE  id = 1

Ancak bu tür UPDATE, farklı satırlar için farklı özellikler değiştirildiğinde toplu güncellemelerden veya deyim önbelleğinden her zaman yararlanamayabilir.


1
Veritabanı PostgreSQL (veya MVCC kullanan bazı diğerleri ) ise,UPDATE a pratik olarak bir DELETE+ değerine eşdeğerdir INSERT(çünkü satırın yeni bir V sürümünü oluşturursunuz). Tepegöz yüksektir ve özellikle bunları içeren sütunların çoğu gerçekten güncellenirse ve dizini temsil etmek için kullanılan ağacın (veya herhangi bir şeyin) önemli bir değişikliğe ihtiyacı varsa dizin sayısı ile birlikte büyür . Alakalı olanın güncellenen sütun sayısı değil, bir dizinin sütun kısmını güncelleyip güncellemediğiniz.
joanolo

@joanolo Bunun yalnızca postgreslerin MVCC uygulaması için geçerli olması gerekir. MySQL, Oracle (ve diğerleri) yerinde bir güncelleme yapar ve değiştirilen sütunları UNDO alanına yeniden konumlandırır.
Morgan Tocker

2
İyi bir ORM'nin hangi sütunların güncellenmesi gerektiğini izlemesi ve veritabanına gönderilen ifadeyi optimize etmesi gerektiğini belirtmeliyim. Yalnızca DB'ye iletilen veri miktarı için, özellikle sütunlardan bazıları uzun metinler veya BLOB'lar olduğunda önemlidir .
joanolo

1
SQL Server için bunu tartışan soru dba.stackexchange.com/q/114360/3690
Martin Smith

2
Hangi DBMS'yi kullanıyorsunuz?
a_horse_with_no_name

Yanıtlar:


12

UPDATEÇoğunlukla performanstan ve çoğunlukla performanstan endişe duyduğunuzu biliyorum , ancak bir "ORM" destekçisi olarak size "değiştirilmiş" , "boş" ve "varsayılan" değerleri ayırt etme sorununa başka bir bakış açısı sunayım. SQL'de üç farklı şey, ancak Java'da ve çoğu ORM'de muhtemelen tek bir şey:

Gerekçenizi INSERTifadelere çevirme

Toplu işlenebilirlik ve ifade önbelleğe alınabilirliği konusundaki argümanlarınız, ifadeler için INSERTolduğu gibi UPDATEifadeler için de geçerlidir. Ancak INSERTifadeler söz konusu olduğunda, ifadeden bir sütunu atlamak, içinde olduğundan farklı bir anlambilime sahiptir UPDATE. Uygulamak demektir DEFAULT. Aşağıdaki ikisi anlamsal olarak eşdeğerdir:

INSERT INTO t (a, b)    VALUES (1, 2);
INSERT INTO t (a, b, c) VALUES (1, 2, DEFAULT);

UPDATEİlk ikisinin anlamsal olarak eşdeğer olduğu ve üçüncüsünün tamamen farklı bir anlamı olduğu için bu doğru değildir :

-- These are the same
UPDATE t SET a = 1, b = 2;
UPDATE t SET a = 1, b = 2, c = c;

-- This is different!
UPDATE t SET a = 1, b = 2, c = DEFAULT;

JDBC ve sonuç olarak JPA dahil olmak üzere çoğu veritabanı istemcisi API'si, bir DEFAULTifadeyi bağlama değişkenine bağlamaya izin vermez - çoğunlukla sunucular buna izin vermez. Yukarıda belirtilen toplu işlenebilirlik ve ifade önbelleğe alınabilirlik nedenleriyle aynı SQL ifadesini yeniden kullanmak istiyorsanız, her iki durumda da aşağıdaki ifadeyi kullanırsınız ( (a, b, c)tüm sütunlar varsa t):

INSERT INTO t (a, b, c) VALUES (?, ?, ?);

Ve cayarlanmadığından, muhtemelen Java'yı nullüçüncü bağlama değişkenine bağlarsınız, çünkü birçok ORM aynı zamandaNULL ve ileDEFAULT ( jOOQ , örneğin bir istisna). Sadece Java'yı görürler nullve bunun NULL(bilinmeyen değerde DEFAULTolduğu gibi ) veya (başlatılmamış değerdeki gibi ) anlamına gelip gelmediğini bilmezler .

Çoğu durumda, bu ayrım önemli değildir, ancak c sütununuz aşağıdaki özelliklerden herhangi birini kullanıyorsa, ifade yanlıştır :

  • Bir DEFAULTmaddesi var
  • Bir tetikleyici tarafından oluşturulmuş olabilir

UPDATEİfadelere geri dön

Yukarıdakiler tüm veritabanları için geçerli olsa da, tetikleyici sorununun Oracle veritabanı için de doğru olduğundan emin olabilirsiniz. Aşağıdaki SQL'i düşünün:

CREATE TABLE x (a INT PRIMARY KEY, b INT, c INT, d INT);

INSERT INTO x VALUES (1, 1, 1, 1);

CREATE OR REPLACE TRIGGER t
  BEFORE UPDATE OF c, d
  ON x
BEGIN
  IF updating('c') THEN
    dbms_output.put_line('Updating c');
  END IF;
  IF updating('d') THEN
    dbms_output.put_line('Updating d');
  END IF;
END;
/

SET SERVEROUTPUT ON
UPDATE x SET b = 1 WHERE a = 1;
UPDATE x SET c = 1 WHERE a = 1;
UPDATE x SET d = 1 WHERE a = 1;
UPDATE x SET b = 1, c = 1, d = 1 WHERE a = 1;

Yukarıdakileri çalıştırdığınızda, aşağıdaki çıktıyı göreceksiniz:

table X created.
1 rows inserted.
TRIGGER T compiled
1 rows updated.
1 rows updated.
Updating c

1 rows updated.
Updating d

1 rows updated.
Updating c
Updating d

Gördüğünüz gibi, her zaman tüm sütunları güncelleyen ifade her zaman tüm sütunlar için tetikleyiciyi tetiklerken, yalnızca değişen sütunları güncelleyen ifadeler yalnızca bu tür belirli değişiklikleri dinleyen tetikleyicileri tetikler.

Diğer bir deyişle:

Hazırladığınız Hibernate'in mevcut davranışı eksiktir ve hatta tetikleyicilerin (ve muhtemelen diğer araçların) varlığında yanlış kabul edilebilir.

Şahsen dinamik SQL durumunda sorgu önbellek optimizasyonu argümanının abartıldığını düşünüyorum. Elbette, böyle bir önbellekte birkaç sorgu daha yapılacak ve biraz daha ayrıştırma çalışması yapılacak, ancak bu genellikle dinamik UPDATEifadeler için olduğundan daha az bir sorun değildir SELECT.

Toplu işlem kesinlikle bir sorundur, ancak benim görüşüme göre, tüm sütunları güncellemek için tek bir güncelleme normalleştirilmemelidir, çünkü ifadenin küçük bir olasılığı olabilir. Muhtemelen ORM, ardışık özdeş ifadelerin alt gruplarını toplayabilir ve "tüm toplu iş" yerine bunları toplu olarak işleyebilir (ORM'nin "değiştirilmiş" , "boş" ve "varsayılan" arasındaki farkı bile izleyebilmesi durumunda)


DEFAULTKullanım durumu ele alınabilir @DynamicInsert. TRIGGER durumu, gibi kontroller kullanılarak WHEN (NEW.b <> OLD.b)veya sadece geçiş yapmak için de ele alınabilir @DynamicUpdate.
Vlad Mihalcea

Evet, işler ele alınabilir, ancak başlangıçta performans hakkında sorular soruyordunuz ve geçici çözümünüz daha da fazla ek yük getiriyor.
Lukas Eder

Sanırım Morgan bunu en iyi şekilde söyledi: karmaşık .
Vlad Mihalcea

Bence oldukça basit. Çerçeve perspektifinden bakıldığında, dinamik SQL'e geçme lehine daha fazla argüman var. Kullanıcı açısından, evet, karmaşık.
Lukas Eder

9

Bence cevap - karmaşık . MySQL'de bir longtextsütun kullanarak hızlı bir kanıt yazmaya çalıştım , ancak cevap biraz sonuçsuz. Önce kanıt:

# in advance:
set global max_allowed_packet=1024*1024*1024;

CREATE TABLE `t2` (
  `a` int(11) NOT NULL AUTO_INCREMENT,
  `b` char(255) NOT NULL,
  `c` LONGTEXT,
  PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

mysql> insert into t2 (a, b, c) values (null, 'b', REPEAT('c', 1024*1024*1024));
Query OK, 1 row affected (38.81 sec)

mysql> UPDATE t2 SET b='new'; # fast
Query OK, 1 row affected (6.73 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql>  UPDATE t2 SET b='new'; # fast
Query OK, 0 rows affected (2.87 sec)
Rows matched: 1  Changed: 0  Warnings: 0

mysql> UPDATE t2 SET b='new'; # fast
Query OK, 0 rows affected (2.61 sec)
Rows matched: 1  Changed: 0  Warnings: 0

mysql> UPDATE t2 SET c= REPEAT('d', 1024*1024*1024); # slow (changed value)
Query OK, 1 row affected (22.38 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> UPDATE t2 SET c= REPEAT('d', 1024*1024*1024); # still slow (no change)
Query OK, 0 rows affected (14.06 sec)
Rows matched: 1  Changed: 0  Warnings: 0

Bu yüzden yavaş + değişen değer ile yavaş + değişen değer arasında küçük bir zaman farkı vardır. Bu yüzden yazılan sayfalar olan başka bir metriğe bakmaya karar verdim:

mysql> show global status like 'innodb_pages_written';
+----------------------+--------+
| Variable_name        | Value  |
+----------------------+--------+
| Innodb_pages_written | 198656 |
+----------------------+--------+
1 row in set (0.00 sec)

mysql> show global status like 'innodb_pages_written';
+----------------------+--------+
| Variable_name        | Value  |
+----------------------+--------+
| Innodb_pages_written | 198775 | <-- 119 pages changed in a "no change"
+----------------------+--------+
1 row in set (0.01 sec)

mysql> show global status like 'innodb_pages_written';
+----------------------+--------+
| Variable_name        | Value  |
+----------------------+--------+
| Innodb_pages_written | 322494 | <-- 123719 pages changed in a "change"!
+----------------------+--------+
1 row in set (0.00 sec)

Bu nedenle, zamanın arttığı görülüyor, çünkü değerin kendisinin değiştirilmediğini doğrulamak için bir karşılaştırma olması gerekiyor, bu da 1G uzun metinde zaman alıyor (çünkü birçok sayfaya bölündüğü için). Ancak modifikasyonun kendisi yineleme günlüğü boyunca çalkalanmıyor gibi görünüyor.

Değerler sayfa içi normal sütunlar ise karşılaştırmanın sadece biraz ek yük getirdiğinden şüpheleniyorum. Ve aynı optimizasyonun geçerli olduğu varsayılarak, güncelleme söz konusu olduğunda bunlar işlem yapılmaz.

Daha Uzun Cevap

Aslında , bu optimizasyonun garip yan etkileri olduğu için ORM'in değiştirilmiş ( ancak değiştirilmemiş ) sütunları ortadan kaldırmaması gerektiğini düşünüyorum .

Sözde kodda aşağıdakileri göz önünde bulundurun:

# Initial Data does not make sense
# should be either "Harvey Dent" or "Two Face"

id: 1, firstname: "Two Face", lastname: "Dent"

session1.start
session2.start

session1.firstname = "Two"
session1.lastname = "Face"
session1.save

session2.firstname = "Harvey"
session2.lastname = "Dent"
session2.save

ORM'nin değişiklik yapılmadan değişiklik yapılmasını "optimize etmesi" sonucu:

id: 1, firstname: "Harvey", lastname: "Face"

ORM tüm değişiklikleri sunucuya gönderirse sonuç:

id: 1, firstname: "Harvey", lastname: "Dent"

Buradaki test senaryosu yalıtıma repeatable-read(MySQL varsayılanı) dayanır , ancak read-committedsession2 okumasının session1 tamamlanmadan önce gerçekleştiği yalıtım için de bir zaman penceresi vardır .

Başka bir deyişle: optimizasyon yalnızca a SELECT .. FOR UPDATEsatırları ve ardından bir okur UPDATE. SELECT .. FOR UPDATEMVCC kullanmaz ve her zaman satırların en son sürümünü okur.


Düzenleme: Test durumu veri kümesinin% 100 bellekte olduğundan emin olun. Düzeltilmiş zamanlama sonuçları.


Açıklama için teşekkürler. Benim de sezgim bu. Bence veri sayfasındaki satırı ve ilgili tüm dizinleri kontrol edecektir. Sütun çok büyükse veya tonlarca endeks varsa, ek yük fark edilebilir hale gelebilir. Ancak çoğu durumda, kompakt sütun türlerini ve gerektiği kadar dizin kullanırken, ek yükün ifadenin önbelleğe alınmasından veya ifadenin toplu işlenmesi için daha az şansa sahip olmamasından daha az olabileceğini düşünüyorum.
Vlad Mihalcea

1
@VladMihalcea cevabın MySQL ile ilgili olduğuna dikkat edin. Sonuçlar farklı DBMS'lerde aynı olmayabilir.
ypercubeᵀᴹ

@ypercube Bunun farkındayım. Her şey RDBMS'ye bağlıdır.
Vlad Mihalcea
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.