Nasıl mysql önlemek için 'Kilitlenmeye çalışırken kilitlenme bulundu; işlemi yeniden başlatmayı deneyin '


286

Çevrimiçi kullanıcıları kaydeden bir innoDB tablo var. Hangi sayfalarda olduklarını ve siteye son erişim tarihlerini takip etmek için her sayfa yenilemesinde kullanıcı tarafından güncellenir. Sonra eski kayıtları silmek için her 15 dakikada bir çalışan bir cron var.

Kilitlenmeye çalışırken bir kilitlenme buldum; işlemi dün gece yaklaşık 5 dakika yeniden başlatmayı deneyin ve INSERT'leri bu tabloya çalıştırırken olduğu görülüyor. Birisi bu hatadan nasıl kaçınılacağını önerebilir mi?

=== DÜZENLE ===

Çalışan sorgular şunlardır:

Siteye İlk Ziyaret:

INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3

Her sayfada yenileme:

UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888

Her 15 dakikada bir Cron:

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

Daha sonra bazı istatistikleri kaydetmek için bazı sayılar yapar (örneğin: çevrimiçi üyeler, çevrimiçi ziyaretçiler).


Masa yapısı hakkında biraz daha ayrıntı verebilir misiniz? Kümelenmiş veya kümelenmemiş dizinler var mı?
Anders Abel

13
dev.mysql.com/doc/refman/5.1/tr/innodb-deadlocks.html - "Motor innodb durumunu göster" komutunu çalıştırmak yararlı tanılama sağlar.
Martin

Kullanıcılar bir sayfaya adım attığında senkronize bir veritabanı yazma yapmak iyi bir uygulama değildir. bunu yapmak için doğru yolu memcache veya bazı hızlı kuyruk gibi bellekte tutmak ve cron ile db yazmaktır.
Nir

Yanıtlar:


292

Çoğu kilitlenme ile yardımcı olabilecek kolay bir hile, işlemleri belirli bir sırayla sıralamaktır.

İki işlem zıt siparişlerde iki kilidi kilitlemeye çalıştığında bir kilitlenme oluşur, yani:

  • bağlantı 1: kilit anahtarı (1), kilit anahtarı (2);
  • bağlantı 2: kilit anahtarı (2), kilit anahtarı (1);

Her ikisi de aynı anda çalışırsa, bağlantı 1 anahtarı (1) kilitler, bağlantı 2 anahtar (2) kilitler ve her bağlantı diğerinin anahtarı serbest bırakmasını bekler -> kilitlenme.

Şimdi, sorgularınızı bağlantılar anahtarları aynı sırada kilitleyecek şekilde değiştirdiyseniz, yani:

  • bağlantı 1: kilit anahtarı (1), kilit anahtarı (2);
  • bağlantı 2: kilit anahtarı ( 1 ), kilit anahtarı ( 2 );

çıkmaz almak imkansız.

Yani önerdiğim bu:

  1. Delete deyimi dışında bir kerede birden fazla anahtara erişimi kilitleyen başka sorgunuz olmadığından emin olun. yaparsanız (ve yaptığınızdan şüpheleniyorsanız), WHERE (K1, k2, .. kn) cinsinden artan sırada sipariş edin.

  2. Silme ifadenizi artan sırada çalışacak şekilde düzeltin:

Değişiklik

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

için

DELETE FROM onlineusers WHERE id IN (SELECT id FROM onlineusers
    WHERE datetime <= now() - INTERVAL 900 SECOND order by id) u;

Akılda tutulması gereken başka bir şey, mysql belgelerinin bir kilitlenme durumunda istemcinin otomatik olarak yeniden denemesi gerektiğini önermesidir. bu mantığı istemci kodunuza ekleyebilirsiniz. (Diyelim ki 3, vazgeçmeden önce bu özel hatayı yeniden dener).


2
İşlemim varsa (autocommit = false), kilitlenme istisnası atılır. Sadece aynı ifadeyi yeniden denemek için yeterli mi?
Whome

5
etkinleştirilmiş işlemleriniz varsa, hepsi ya da hiçbir şey değildir. herhangi bir istisna varsa, tüm işlemin bir etkisi olmadığı garanti edilir. bu durumda her şeyi yeniden başlatmak istersiniz.
Omry Yadan

4
Büyük bir tablodaki
seçime dayalı

3
Çok teşekkürler dostum. 'Sıralama ifadeleri' ipucu ölü kilit sorunlarımı düzeltti.
Miere

4
@OmryYadan Bildiğim kadarıyla, MySQL'de UPDATE yaptığınız aynı tablodan bir alt sorgu seçemezsiniz. dev.mysql.com/doc/refman/5.7/en/update.html
artaxerxe

73

Kilitlenme için iki işlem birbirini beklediğinde kilitlenme meydana gelir. Misal:

  • Tx 1: A kilidi sonra B
  • Tx 2: B kilidi, sonra A

Kilitlenmeler hakkında çok sayıda soru ve cevap var. Her satır eklediğinizde / güncellediğinizde veya sildiğinizde bir kilit alınır. Kilitlenmeyi önlemek için, eşzamanlı işlemlerin bir kilitlenme ile sonuçlanabilecek bir sırayla satırı güncellemediğinden emin olmalısınız. Genel olarak konuşursak, farklı işlemlerde bile her zaman aynı sırada kilit almaya çalışın (örn. Her zaman önce tablo A, sonra tablo B).

Veritabanındaki kilitlenmenin bir başka nedeni eksik dizinler olabilir . Bir satır eklendiğinde / güncelleştirildiğinde / silindiği zaman, veritabanının ilişkisel kısıtlamaları kontrol etmesi, yani ilişkilerin tutarlı olduğundan emin olması gerekir. Bunu yapmak için, veritabanının ilgili tablolardaki yabancı anahtarları kontrol etmesi gerekir. Bu olabilir değiştirilir sıranın dışında edinilen diğer kilit yüklenebileceği. Ardından yabancı anahtarlarda (ve tabii ki birincil anahtarlarda) dizin bulunduğundan emin olun, aksi takdirde satır kilidi yerine tablo kilidine neden olabilir . Tablo kilidi gerçekleşirse, kilit çekişmesi daha yüksek olur ve kilitlenme olasılığı artar.


3
Belki de benim sorunum Kullanıcı sayfayı yeniledi ve böylece aynı zamanda cron kayıt üzerinde bir DELETE çalıştırmaya çalışırken aynı zamanda bir kayıt UPDATE tetiklemesidir. Ancak, im ekler üzerinde hata alıyorum, bu yüzden cron yeni oluşturulan kayıtları SİLMEZ. Peki, henüz eklenmemiş bir kayıtta nasıl bir kilitlenme olabilir?
David

Tablo (lar) ve işlemlerin tam olarak ne yaptığı hakkında biraz daha bilgi verebilir misiniz?
ewernli

İşlem başına yalnızca bir ifade varsa nasıl bir kilitlenme olabileceğini görmüyorum. Diğer tablolarda başka işlem yok mu? Özel yabancı anahtarlar veya benzersiz kısıtlamalar yok mu? Art arda silme kısıtlamaları yok mu?
ewernli

hayır, özel bir şey yok ... Sanırım masanın kullanımının doğasına bağlı. bir ziyaretçinin her sayfa yenilemesinde bir satır eklenir / güncellenir. Her seferinde yaklaşık 1000'den fazla ziyaretçi var.
David

12

Delete deyimi, tablodaki toplam satırların büyük bir bölümünü etkileyecektir. Sonunda bu, silinirken bir tablo kilidinin alınmasına neden olabilir. Bir kilidi tutmak (bu durumda satır veya sayfa kilitleri) ve daha fazla kilit almak her zaman bir kilitlenme riskidir. Ancak ekleme deyiminin neden bir kilit yükselmesine yol açtığını açıklayamıyorum - sayfa bölme / ekleme ile ilgili olabilir, ancak MySQL'i daha iyi bilen biri orada doldurmak zorunda kalacak.

Bir başlangıç ​​için, delete deyimi için hemen bir tablo kilidi almayı denemeye değer. Bkz. KİLİT TABLOLARI ve Tablo kilitleme sorunları .


6

Önce deletebu sözde kod gibi geçici tabloya silinecek her satırın anahtarını ekleyerek bu işin çalışmasını deneyebilirsiniz

create temporary table deletetemp (userid int);

insert into deletetemp (userid)
  select userid from onlineusers where datetime <= now - interval 900 second;

delete from onlineusers where userid in (select userid from deletetemp);

Bu şekilde kırmak daha az verimlidir, ancak sırasında anahtar aralığı kilidini tutma ihtiyacını ortadan kaldırır delete.

Ayrıca, 900 saniyeden eski satırlar dışında selectbir wherecümle eklemek için sorgularınızı değiştirin . Bu, cron işine bağımlılığı önler ve daha az çalışacak şekilde yeniden planlamanızı sağlar.

Kilitlenmeler hakkında teori: MySQL'de çok fazla arka planım yok ama işte gidiyor ... İşlemin ortasında yan deletetümcesiyle eşleşen satırların eklenmesini önlemek için datetime için bir anahtar aralığı kilidi tutacak whereve silinecek satırları bulduğunda değiştirdiği her sayfada bir kilit almaya çalışır. insertO takmadan sayfa üzerinde kilit elde etmek gidiyor ve sonra tuş kilidi almaya çalışır. Normalde insertbu anahtar kilidi açmak için sabırla bekleyecek ama eğer bu kilitlenmeye deleteçalışır aynı sayfada kilitlemek için insertkullandığından deletesayfa kilidi o ihtiyaçları ve insertanahtar kilidi olduğu ihtiyaçlar. Bu, kesici uçlar için doğru görünmüyor, deleteveinsert belki de başka bir şey oluyor, örtüşmeyen datetime aralıkları kullanıyor.

http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html


4

Birinin hala bu sorunla mücadele etmesi durumunda:

Ben 2 istek aynı anda sunucu vurmak benzer bir sorunla karşı karşıya. Aşağıdaki gibi bir durum yoktu:

T1:
    BEGIN TRANSACTION
    INSERT TABLE A
    INSERT TABLE B
    END TRANSACTION

T2:
    BEGIN TRANSACTION
    INSERT TABLE B
    INSERT TABLE A
    END TRANSACTION

Öyleyse neden kilitlenmenin meydana geldiğini şaşırmıştım.

Sonra yabancı anahtar nedeniyle 2 tablo arasında ebeveyn çocuk ilişkisi gemi bulundu. Alt tabloya bir kayıt eklerken, işlem üst tablonun satırında bir kilit alıyordu. Bundan hemen sonra, kilidin ÖZEL olana yükseltilmesini tetikleyen üst satırı güncellemeye çalışıyordum. 2. eşzamanlı işlem zaten bir SHARED kilidi tuttuğu için kilitlenmeye neden oluyordu.

Bakınız: https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html


Benim durumumda da, sorunun yabancı bir anahtar ilişkisi olduğu anlaşılıyor. Thanks1
Chris Prince

3

Spring kullanan Java programcıları için, geçici kilitlenmelerde çalışan işlemleri otomatik olarak yeniden deneyen AOP yönünü kullanarak bu sorunu önledim.

Daha fazla bilgi için @RetryTransaction Javadoc sayfasına bakın .


0

İç MySqlTransaction sarılmış bir yöntem var.

Aynı yöntemi kendime paralel olarak çalıştırdığımda kilitlenme sorunu ortaya çıktı.

Yöntemin tek bir örneğini çalıştırırken bir sorun olmadı.

MySqlTransaction'ı kaldırdığımda, yöntemi kendisiyle paralel olarak sorunsuz bir şekilde çalıştırabildim.

Sadece deneyimlerimi paylaşıyorum, hiçbir şey savunmuyorum.


0

crontehlikeli. Bir cron örneği diğeri bitmeden bitmezse, muhtemelen birbirleriyle savaşacaklardır.

Bazı satırları silen, biraz uyuyan ve tekrarlayan sürekli çalışan bir işe sahip olmak daha iyi olur.

Ayrıca, INDEX(datetime)kilitlenmeleri önlemek için çok önemlidir.

Ancak, datetime testi, tablonun% 20'sinden fazlasını içeriyorsa, DELETEtablo taraması yapar. Daha sık silinen daha küçük parçalar bir çözümdür.

Daha küçük parçalarla gitmenin bir başka nedeni, daha az sayıda satırı kilitlemektir.

Sonuç olarak:

  • INDEX(datetime)
  • Sürekli çalışan görev - silin, bir dakika uyuyun, tekrarlayın.
  • Yukarıdaki görevin ölmediğinden emin olmak için, tek amacı başarısızlık durumunda yeniden başlatmak olan bir cron işi yapın.

Diğer silme teknikleri: http://mysql.rjweb.org/doc.php/deletebig

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.