2010-09-29 20 views
42

SQL ve kilitleme stratejileri hakkında bir sorum var. Örnek olarak, web sitemdeki resimler için bir görüntüleme sayacım olduğunu varsayalım. Ben şu ifadeleri gerçekleştirmek için benzer bir sproc veya varsa: SQL atomik arttırma ve kilitleme stratejileri - bu güvenli midir?

START TRANSACTION; 
UPDATE images SET counter=counter+1 WHERE image_id=some_parameter; 
COMMIT; 

belirli image_id için sayaç değerini '0' t0 zamanında sahip olduğunu varsayalım. Aynı seans sayacını s1 ve s2 güncelleyen iki seans, t0 ile eşzamanlı olarak başlıyorsa, bu iki seansın her ikisinin de '0' değerini okuduğunu, '1'e yükselteceğini ve her ikisinin de sayacı' 1'e güncellemeye çalışabilme şansı var mıdır? 'Yani, sayaç' 2 'yerine' 1 'değerini alacak?

s1: begin 
s1: begin 
s1: read counter for image_id=15, get 0, store in temp1 
s2: read counter for image_id=15, get 0, store in temp2 
s1: write counter for image_id=15 to (temp1+1), which is 1 
s2: write counter for image_id=15 to (temp2+1), which is also 1 
s1: commit, ok 
s2: commit, ok 

Sonuç: Yanlış değer '1' image_id = 15 için,

Benim sorulara 2. olmalıydı şunlardır:

  1. bu senaryonun mümkün mü?
  2. Böyle bir durumda, işlem yalıtımı düzeyi önemli midir?
  3. Böyle bir çakışmayı bir hata olarak algılayacak bir çakışma çözümleyicisi var mı?
  4. Sorunlardan kaçınmak için herhangi bir özel sözdizimini kullanabilir (Karşılaştırma ve Takas (CAS) veya açık kilitleme teknikleri gibi)?

Genel bir yanıtla ilgileniyorum, ancak MySql ve InnoDB'ye özel yanıtlarla ilgilenmiyorum, çünkü InnoDB'de dizileri uygulamak için bu tekniği kullanmaya çalıştığım için.

DÜZENLEME: Aşağıdaki senaryo da aynı davranışla sonuçlanabilir. Yalıtım seviyesinde READ_COMMITED ya da daha yüksek olduğumuzu varsayıyorum, bu yüzden s2 işlemin başlangıcından itibaren değeri almasına rağmen s1 zaten sayacı '1' yazdı.

s1: begin 
s1: begin 
s1: read counter for image_id=15, get 0, store in temp1 
s1: write counter for image_id=15 to (temp1+1), which is 1 
s2: read counter for image_id=15, get 0 (since another tx), store in temp2 
s2: write counter for image_id=15 to (temp2+1), which is also 1 
s1: commit, ok 
s2: commit, ok 
+0

MySQL http://stackoverflow.com/questions/4358732/is-incrementing-a-field-in-mysql-atomic || MS http://stackoverflow.com/questions/193257/in-ms-sql-server-is-there-a-way-to-atomically-increment-a-column-being-used-a –

cevap

28

UPDATE sorgu okur sayfaları veya kayıtlarında bir güncelleme kilidi yerleştirir.

Kaydın güncellenip güncellenmeyeceğine karar verildiğinde, kilit kaldırılır veya özel kilitlenmeye yükseltilir. s1 sayacı veya olmasın yazmaya karar verir ve bu senaryo aslında imkansız kadar

s1: read counter for image_id=15, get 0, store in temp1 
s2: read counter for image_id=15, get 0, store in temp2 
s1: write counter for image_id=15 to (temp1+1), which is 1 
s2: write counter for image_id=15 to (temp2+1), which is also 1 

s2 bekler:

Bu, bu senaryoda anlamına gelir.

Bu olacak: InnoDB yılında, DML sorguları okudukları kayıtlarından güncelleme kilitleri yükselmeyecek

s1: place an update lock on image_id = 15 
s2: try to place an update lock on image_id = 15: QUEUED 
s1: read counter for image_id=15, get 0, store in temp1 
s1: promote the update lock to the exclusive lock 
s1: write counter for image_id=15 to (temp1+1), which is 1 
s1: commit: LOCK RELEASED 
s2: place an update lock on image_id = 15 
s2: read counter for image_id=15, get 1, store in temp2 
s2: write counter for image_id=15 to (temp2+1), which is 2 

Not.

Bu, tam bir tablo taraması durumunda, okunan ancak güncelleştirilmemeye karar verilen kayıtların, işlemin sonuna kadar kilitli kalacağı ve başka bir işlemden güncellenemediği anlamına gelir.

+1

Harika bir açıklama için teşekkürler. Bence buradaki anahtar deyim 'taahhüt: LOCK RELEASED'. Bu, satırı güncellemek isteyen tüm işlemlerin, kilidi tutan işlemin tamamlanmasını beklemek zorunda kalması, satır için geçerli olan tüm işlemleri etkin bir şekilde serileştirmesi anlamına gelir. Bu Postgres gibi bir çoklu sürüm eşzamanlılık db içinde nasıl çalışır biliyor musunuz? Birden çok sürüm kullandığı için, işlemlerin bağımsız olarak ilerlemesine izin verecek ve sonuçları birleştirmeye çalışacağımı varsayabilirim. Yoksa aynı stratejiyi kullanıyor mu? –

+1

S1'in cevabında ConcernedOfTunbridgeWells tarafından önerilen şekilde bir güncelleme kilidi yerleştirmek için gerekli minimum bir işlem yalıtım düzeyi var mı? –

+0

@disown: PostgreSQL'in kullandığı MVCC'de kilitleme kavramı yok. Bunun yerine, bir işaretleyici olarak işlem tanımlayıcıyı kullanarak bir kaydın birden çok sürümü depolanır. Kilitleme, yalnızca bir sürümdeki bir sürümü değiştirmeye çalışırken oluşur. – Quassnoi

8

kilitleme düzgün yapılmazsa kesinlikle bu tip yarış durumu almak mümkündür ve varsayılan kilit modu (taahhüt okuyun) buna izin vermez. Bu modda, okumalar yalnızca kayıt üzerinde bir paylaşımlı kilit yerleştirir, böylece her ikisi de 0 değerini görebilir, artırabilir ve veri tabanına 1 yazabilirler.

Bu yarış koşulundan kaçınmak için, okuma işleminde özel bir kilit ayarlamanız gerekir. 'Serializable' ve 'Repeatable Read' eşzamanlılık modları bunu yapacak ve tek bir satırdaki bir işlem için hemen hemen eşdeğerdirler.

Mecbur tamamen atomik yapmak için:

  • Serializable'için gibi uygun transaction isolation level böyle ayarlayın. Normalde bunu istemci kütüphanesinden veya SQL'de explicilty'den yapabilirsiniz.
  • o
  • işlem yapar veri
  • Güncelleme Oku işlem
  • başlayın.

Ayrıca, SQL lehinize bağlı olarak okunan HOLDLOCK (T-SQL) veya eşdeğer bir ipucu ile özel bir kilidi zorlayabilirsiniz.

Tek bir güncelleştirme sorgusu bunu atomik olarak yapar, ancak okumaların özel bir kilidi almasını engellemeden işlemi bölemezsiniz (belki de değeri okumak ve istemciye geri döndürmek). Bir sıralamayı uygulamak için atomik olarak almanız gerekecek, böylece güncellemenin kendisi muhtemelen ihtiyacınız olan tam olarak değil. Atomik güncellemeyle bile, güncellemeden sonra değeri okumak için hala bir yarış koşulunuz var. Okuma hala bir işlemin içinde (bir değişkendeki şeyi saklamak) gerçekleşmeli ve okuma sırasında özel bir kilit açacaktır.

Veritabanınızı kaydetmeden önce, veritabanınızın saklı yordam içinde autonomous (nested) transactions için uygun desteğe sahip olması gerektiğini unutmayın. Bazen 'iç içe', zincirleme işlemlerine başvurmak veya puan kazanmak için kullanılır, böylece terim biraz kafa karıştırıcı olabilir. Bunu özerk işlemlere başvurmak için düzenledim. özerk işlemler olmadan

senin kilitleri sürü geri alabilirsiniz ana işlem, tarafından devralınır. Bu, ana işlem taahhüt edilene kadar tutulacakları anlamına gelir, bu da sıranızı bu sırayı kullanarak tüm işlemleri seri hale getiren bir sıcak noktaya dönüştürebilir. Diziyi kullanmaya çalışan başka bir şey, tüm ana işlem taahhüt edilene kadar engellenir.

IIRC Oracle, otonom işlemlerini destekler, ancak DB/2, oldukça yakın zamana kadar ve SQL Server bunu yapmaz. Kafamın üst kısmından, InnoDB'nin onları destekleyip desteklemediğini bilmiyorum ama Grey and Reuter, ne kadar zor uygulandıklarına dair bir süre devam ediyor. Pratikte tahmin edemeyeceğini tahmin ediyorum. YMMV.

+0

Tek 'UPDATE ile 'sorgulama, hangi işlem yalıtım seviyesi kullanılır olursa olsun, bu yarış koşulunu, işlemleri destekleyen ana sistemlerden herhangi birinde elde etmek mümkün değildir. – Quassnoi

+0

Sorunu doğru bir şekilde anlarsam, yalıtım yeterli olmaz, SERIALIZABLE olabilir, ancak anlık görüntü yalıtımı olmayabilir (http://en.wikipedia.org/wiki/Snapshot_isolation), çünkü 'tanımı hakkında emin değilim' çatışma bu durumda. İki kişi çatışıyor mu, değil mi? Ve güncelleme sırasında satırda özel bir kilidiniz olsa bile, s2: s güncellemesi s1: s güncelleme ve commit arasında geliyorsa ne olur?O zaman s2 ne okuyacak? Muhtemelen 0. Bu durumda doğru davranışı sağlamak için görebildiğim tek yol, s1 işleminin sonuna kadar özel kilidi tutmasıdır. –

+0

@Quassnoi: Güncellemeden sonra ve işlemden önce başka bir işlem ne okunur? Örneğin, READ_COMMITED varsa, diğer işlemin '1' değerine bakmasına izin vermek yanlış olur (diğer işleme bakmak). '0', diğer eşzamanlı işlemin görebildiği tek diğer mantıksal değerdir. Yani ya bu yarış durumu oluşabiliyor olmalı (ve muhtemelen bir hatayla sonuçlanacaktır), ya da db işlemleri bir şekilde serileştirmek zorundadır. –