InnoDB sıra kilitleme - nasıl uygulanır


13

Şimdi etrafa bakıyorum, mysql sitesini okuyorum ve hala tam olarak nasıl çalıştığını göremiyorum.

Yazmak için sonucu seçmek ve sıra kilitlemek, değişikliği yazmak ve kilidi serbest bırakmak istiyorum. audocommit açık.

düzen

id (int)
name (varchar50)
status (enum 'pending', 'working', 'complete')
created (datetime)
updated (datetime) 

Beklemede olan bir öğe seçin ve öğeyi çalışmaya güncelleyin. Aynı öğenin iki kez alınmadığından emin olmak için özel bir yazma kullanın.

yani;

"SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR WRITE"

kimliği sonuçtan al

"UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>

Kilidi açmak için bir şey yapmam gerekiyor mu ve yukarıda yaptığım gibi çalışıyor mu?

Yanıtlar:


26

İstediğiniz şey , bir işlem bağlamında GÜNCELLEME İÇİN SEÇ ... 'i seçmektir . GÜNCELLEME SEÇ, tıpkı GÜNCELLEME yürütüyormuşsunuz gibi seçilen satırlara özel bir kilit koyar. Ayrıca, yalıtım düzeyinin açıkça ne ayarlandığına bakılmaksızın, ÖĞRENMİŞ TALİMATI yalıtım düzeyinde çalışır. SELECT ... FOR UPDATE'in eşzamanlılık için çok kötü olduğunu ve yalnızca kesinlikle gerekli olduğunda kullanılması gerektiğini unutmayın. Ayrıca, insanlar kesip yapıştırdıkça bir kod tabanında çoğalma eğilimi gösterir.

Sakila veritabanından FOR UPDATE sorgularının bazı davranışlarını gösteren örnek bir oturum.

İlk olarak, kristal netliğinde işlem yalıtım seviyesini REPEATABLE READ olarak ayarlayın. InnoDB için varsayılan yalıtım seviyesi olduğundan bu normalde gereksizdir:

session1> SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
session1> BEGIN;
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA      | WILLIAMS  |
+------------+-----------+
1 row in set (0.00 sec)    

Diğer oturumda bu satırı güncelleyin. Linda evlendi ve adını değiştirdi:

session2> UPDATE customer SET last_name = 'BROWN' WHERE customer_id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

1. oturumda, REPEATABLE READ'deyken Linda hala LINDA WILLIAMS:

session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA      | WILLIAMS  |
+------------+-----------+
1 row in set (0.00 sec)

Ama şimdi, bu satıra özel erişim istiyoruz, bu yüzden satırda FORDDATE'i çağırıyoruz. Şimdi bu işlemin dışındaki session2 sürümünde güncellenen satırın en son sürümünü aldığımıza dikkat edin. Bu TEKRARLANABİLİR OKUMA DEĞİL, OKUYAN OKULDU

session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3 FOR UPDATE;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA      | BROWN     |
+------------+-----------+
1 row in set (0.00 sec)

Oturum1'de ayarlanan kilidi test edelim. Session2'nin satırı güncelleyemeyeceğini unutmayın.

session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

Ama yine de seçebiliriz

session2> SELECT c.customer_id, c.first_name, c.last_name, a.address_id, a.address FROM customer c JOIN address a USING (address_id) WHERE c.customer_id = 3;
+-------------+------------+-----------+------------+-------------------+
| customer_id | first_name | last_name | address_id | address           |
+-------------+------------+-----------+------------+-------------------+
|           3 | LINDA      | BROWN     |          7 | 692 Joliet Street |
+-------------+------------+-----------+------------+-------------------+
1 row in set (0.00 sec)

Ve yine de yabancı anahtar ilişkisi olan bir çocuk tablosunu güncelleyebiliriz

session2> UPDATE address SET address = '5 Main Street' WHERE address_id = 7;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

session1> COMMIT;

Bir başka yan etki, bir kilitlenmeye neden olma olasılığınızı büyük ölçüde artırmanızdır.

Özel durumunuzda, muhtemelen:

BEGIN;
SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR UPDATE;
-- do some other stuff
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>;
COMMIT;

"Başka şeyler yapın" parçası gereksizse ve aslında satır hakkında bilgi tutmanız gerekmiyorsa, GÜNCELLEME SEÇİN gereksiz ve israflıdır ve bunun yerine bir güncelleme çalıştırabilirsiniz:

UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;

Umarım bu biraz mantıklıdır.


3
Teşekkürler. İki iş parçacığı "SELECT id FROM itemsWHERE status= 'bekleyen' LIMIT 1 FOR UPDATE; ve ikisi de aynı satırı görür, sonra biri diğerini kilitler. Bir şekilde kilitli satırı atlatmayı ve bekleyen bir sonraki öğeye gitmeyi umuyordum ..
Wizzard

1
Veritabanlarının doğası tutarlı veriler döndürmeleridir. Değer güncellenmeden önce bu sorguyu iki kez yürütürseniz, aynı sonucu geri alırsınız. "Satır kilitli olmadıkça" bu sorgu ile eşleşen ilk değeri al "SQL uzantısı yok. İlişkisel bir veritabanının üzerine bir kuyruk uyguluyormuşsunuz gibi geliyor. Durum bu mu?
Aaron Brown

Aaron; evet bunu yapmaya çalışıyorum. Gearman gibi bir şey kullanmaya baktım - ama bu bir baskıydı. Aklınızda başka bir şey var mı?
Wizzard

Bunu okumalısınız bence: engineyard.com/blog/2011/… - mesaj kuyrukları için, tercih ettiğiniz müşteri diline bağlı olarak bunlardan bir sürü var. ActiveMQ, Resque (Ruby + Redis), ZeroMQ, RabbitMQ, vb.
Aaron Brown

1. oturumdaki güncelleme tamamlanana kadar 2. oturumun okumayı engellemesi için bunu nasıl yapabilirim?
CMCDragonkai

2

InnoDB depolama motoru kullanıyorsanız, satır düzeyinde kilitleme kullanır. Çoklu versiyonlama ile birlikte, belirli bir tablo aynı anda farklı istemciler tarafından okunabileceği ve değiştirilebileceği için bu, sorgu eşzamanlılığıyla sonuçlanır. Satır düzeyinde eşzamanlılık özellikleri aşağıdaki gibidir:

Farklı istemciler aynı satırları aynı anda okuyabilir.

Farklı istemciler farklı satırları aynı anda değiştirebilir.

Farklı istemciler aynı satırı aynı anda değiştiremez. Bir işlem bir satırı değiştirirse, diğer işlemler ilk işlem tamamlanana kadar aynı satırı değiştiremez. Diğer işlemler de, UNCOMMITTED READ yalıtım seviyesini kullanmadıkça değiştirilmiş satırı okuyamaz. Yani, orijinal değiştirilmemiş satırı görecekler.

Temel olarak, bazı durumlarda açık kilit hakkında açık kilit ayrıntıları vermeniz gerekebilse de, InnoDB'nin iteslf tarafından açıkça kilitlenmesini belirtmeniz gerekmez:

Aşağıdaki listede mevcut kilit türleri ve etkileri açıklanmaktadır:

OKU

Bir tabloyu okumak için kilitler. READ kilidi, tablodan veri alan SELECT gibi okuma sorguları için bir tabloyu kilitler. Kilidi tutan istemci tarafından bile tabloyu değiştiren INSERT, DELETE veya UPDATE gibi yazma işlemlerine izin vermez. Bir tablo okumak için kilitlendiğinde, diğer istemciler tablodan aynı anda okuyabilir, ancak hiçbir istemci tabloya yazamaz. Okuma kilitli bir tabloya yazmak isteyen bir istemci, o anda okuyan tüm istemcilerin kilitlerini bitirip serbest bırakmasını beklemelidir.

YAZMAK

Bir tabloyu yazmak için kilitler. WRITE kilidi özel bir kilittir. Yalnızca bir tablo kullanılmadığında edinilebilir. Alındıktan sonra, yalnızca yazma kilidini tutan istemci tablodan okuyabilir veya tabloya yazabilir. Diğer müşteriler ne okuyabilir ne de ona yazamazlar. Başka hiçbir müşteri tabloyu okuma veya yazma için kilitleyemez.

YEREL OKUYUN

Bir tabloyu okumak için kilitler, ancak eşzamanlı eklemelere izin verir. Eşzamanlı bir ekleme "okuyucuların yazarlarını engelleme" ilkesine bir istisnadır. Yalnızca MyISAM tabloları için geçerlidir. Bir MyISAM tablosunun ortasında silinmiş veya güncellenmiş kayıtlardan kaynaklanan delik yoksa, ekler her zaman tablonun sonunda gerçekleşir. Bu durumda, bir tablodan okuyan bir istemci, okuma kilidini tutan istemci tablodan okurken diğer istemcilerin tabloya girmesine izin vermek için bir READ LOCAL kilidi ile kilitleyebilir. Bir MyISAM tablosunda delikler varsa, tabloyu birleştirmek için OPTIMIZE TABLOSU'nu kullanarak bunları kaldırabilirsiniz.


Cevap için teşekkürler. Bu tablo ve beklemedeki öğeleri kontrol eden 100 müşteri var gibi ben çok sayıda çarpışma alıyordum - 2-3 müşteri aynı bekleyen satır alıyorum. Masa kilidi yavaşlamak içindir.
Wizzard

0

Başka bir alternatif, son başarılı kilidin zamanını saklayan bir sütun eklemektir ve daha sonra satırı kilitlemek isteyen herhangi bir şeyin, temizlenene veya 5 dakika (veya herhangi bir şey) geçene kadar beklemesi gerekir.

Gibi bir şey...

Schema

id (int)
name (varchar50)
status (enum 'pending', 'working', 'complete')
created (datetime)
updated (datetime)
lastlock (int)

lastlock, unix zaman damgasını karşılaştırması daha kolay (ve belki daha hızlı) olduğu için saklar.

// Anlambilimi özür dilerim, acutally çalıştıklarını kontrol etmedim, ancak yapmazlarsa yeterince yakın olmalılar.

UPDATE items 
  SET lastlock = UNIX_TIMESTAMP() 
WHERE 
  lastlock = 0
  OR (UNIX_TIMESTAMP() - lastlock) > 360;

Daha sonra kaç satırın güncellendiğini kontrol edin, çünkü satırlar aynı anda iki işlemle güncellenemez, satırı güncellerseniz kilidi alırsınız. PHP kullandığınızı varsayarsak, mysql_affected_rows () yöntemini kullanırsınız, bundan geri dönüş 1 ise başarılı bir şekilde kilitlersiniz.

Ardından, yapmanız gerekenleri yaptıktan sonra son kilidi 0 olarak güncelleyebilir veya tembel olabilir ve bir sonraki kilit girişiminin yine de başarılı olacağı zaman 5 dakika bekleyebilirsiniz.

DÜZENLEME: Saatler bir saat geri gidebilir, belki de kontrol geçersiz kılıyor gibi yaz saati değişiklikleri etrafında beklendiği gibi çalışıp çalışmadığını kontrol etmek için biraz çalışma gerekebilir. Unix zaman damgalarının UTC'de olduğundan emin olmanız gerekir - bunlar yine de olabilir.


-1

Alternatif olarak, paralel yazmaya izin vermek ve satır kilidini atlamak için kayıt alanlarını parçalayabilirsiniz (parçalanmış json çiftleri stili). Dolayısıyla, bir bileşik okuma kaydının bir alanı bir tamsayı / gerçekse, o alanın 1-8 parçasına sahip olabilirsiniz (8 yazma kaydı / satır geçerli). Daha sonra, her yazma işleminden sonra parçaları ayrı bir okuma aramasına yuvarlayın. Bu, 8 eşzamanlı kullanıcıya paralel olarak izin verir.

Kısmi bir toplam oluşturan her parçayla çalıştığınız için, çarpışma ve gerçek paralel güncellemeler yoktur (yani, birleştirilmiş okuma kaydının tamamı yerine her parçayı kilitlersiniz). Bu sadece sayısal alanlarda açıkça görülür. Bir sonucu saklamak için matematiksel modifikasyona dayanan bir şey.

Böylece, birleştirilmiş okuma alanı başına birleştirilmiş okuma alanı başına birden çok yazma parçası. Bu sayısal parçalar aynı zamanda ECC, şifreleme ve blok seviyesi aktarımı / depolamasına da borç vermektedir. Yazma parçaları ne kadar fazla olursa, doymuş veriler üzerinde paralel / eşzamanlı yazma erişim hızları o kadar yüksek olur.

MMORPG, çok sayıda oyuncunun Etki Alanı becerileri ile birbirlerine vurmaya başladığında bu sorunla büyük ölçüde acı çekiyor. Bu çoklu oyuncuların hepsinin aynı anda diğer tüm oyuncuları aynı anda yazması / güncellemesi gerekir, böylece birleşik oyuncu kayıtlarında bir yazma satırı kilitleme fırtınası yaratılır.

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.