MySQL InnoDB, READ COMMITTED'de bile silme sırasında birincil anahtarı kilitler


11

önsöz

Uygulamamız, DELETEsorguları paralel olarak yürüten birkaç iş parçacığı çalıştırır . Sorgular izole edilmiş verileri etkiler, yani DELETEaynı satırda ayrı ayrı iş parçacıklarından eşzamanlı oluşma olasılığı olmamalıdır . Bununla birlikte, belgelere göre MySQL DELETEifadeler için hem sonraki anahtarı hem de bazı boşlukları kilitleyen 'sonraki anahtar' kilidi kullanır . Bu şey ölü kilitlere yol açıyor ve bulduğumuz tek çözüm READ COMMITTEDizolasyon seviyesini kullanmak .

Sorun

Büyük tablolar DELETEile karmaşık ifadeler yürütülürken sorun ortaya çıkar JOIN. Belirli bir durumda, yalnızca iki satıra sahip uyarıları içeren bir tablonuz vardır, ancak sorgunun, bazı belirli varlıklara ait tüm uyarıları iki ayrı INNER JOINed tablosundan bırakması gerekir . Sorgu aşağıdaki gibidir:

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
   ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
   ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=? AND dp.dirty_data=1

Day_position tablosu yeterince büyük olduğunda (test durumumda 1448 satır var), READ COMMITTEDyalıtım moduyla bile herhangi bir işlem tüm proc_warnings tabloyu engeller .

Sorun her zaman bu örnek verilerde yeniden oluşturulur - http://yadi.sk/d/QDuwBtpW1BxB9 hem MySQL 5.1 (5.1.59'da kontrol edildi) hem de MySQL 5.5 (MySQL 5.5.24'te kontrol edildi).

DÜZENLEME: Bağlantılı örnek veriler, sorgu tabloları için şema ve dizinleri de içerir ve burada kolaylık olması açısından çoğaltılmıştır:

CREATE TABLE  `proc_warnings` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `transaction_id` int(10) unsigned NOT NULL,
    `warning` varchar(2048) NOT NULL,
    PRIMARY KEY (`id`),
    KEY `proc_warnings__transaction` (`transaction_id`)
);

CREATE TABLE  `day_position` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `transaction_id` int(10) unsigned DEFAULT NULL,
    `sort_index` int(11) DEFAULT NULL,
    `ivehicle_day_id` int(10) unsigned DEFAULT NULL,
    `dirty_data` tinyint(4) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `day_position__trans` (`transaction_id`),
    KEY `day_position__is` (`ivehicle_day_id`,`sort_index`),
    KEY `day_position__id` (`ivehicle_day_id`,`dirty_data`)
) ;

CREATE TABLE  `ivehicle_days` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `d` date DEFAULT NULL,
    `sort_index` int(11) DEFAULT NULL,
    `ivehicle_id` int(10) unsigned DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `ivehicle_days__is` (`ivehicle_id`,`sort_index`),
    KEY `ivehicle_days__d` (`d`)
);

İşlem başına sorgulama aşağıdaki gibidir:

  • İşlem 1

    set transaction isolation level read committed;
    set autocommit=0;
    begin;
    DELETE pw 
    FROM proc_warnings pw 
    INNER JOIN day_position dp 
        ON dp.transaction_id = pw.transaction_id 
    INNER JOIN ivehicle_days vd 
        ON vd.id = dp.ivehicle_day_id 
    WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;
  • İşlem 2

    set transaction isolation level read committed;
    set autocommit=0;
    begin;
    DELETE pw 
    FROM proc_warnings pw 
    INNER JOIN day_position dp 
        ON dp.transaction_id = pw.transaction_id 
    INNER JOIN ivehicle_days vd 
        ON vd.id = dp.ivehicle_day_id 
    WHERE vd.ivehicle_id=13 AND dp.dirty_data=1;

Bunlardan biri her zaman 'Kilit bekleme zaman aşımı aşıldı ...' hatasıyla başarısız oluyor. information_schema.innodb_trxAşağıdaki satırları içerir:

| trx_id     | trx_state   | trx_started           | trx_requested_lock_id  | trx_wait_started      | trx_wait | trx_mysql_thread_id | trx_query |
| '1A2973A4' | 'LOCK WAIT' | '2012-12-12 20:03:25' | '1A2973A4:0:3172298:2' | '2012-12-12 20:03:25' | '2'      | '3089'              | 'DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1' |
| '1A296F67' | 'RUNNING'   | '2012-12-12 19:58:02' | NULL                   | NULL | '7' | '3087' | NULL |

information_schema.innodb_locks

| lock_id                | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |

Gördüğüm gibi her iki sorgu Xda birincil anahtar = 53 olan bir satırda özel bir kilit istiyor. Ancak, bunların hiçbiri proc_warningstablodan satırları silmemelidir. Endeksin neden kilitlendiğini anlamıyorum. Ayrıca, proc_warningstablo boş olduğunda veya day_positiontablo daha az sayıda satır (yani yüz satır) içerdiğinde dizin kilitlenmez .

EXPLAINBenzer SELECTsorgu üzerinde daha fazla araştırma yapılması gerekti. Sorgu iyileştiricinin proc_warningstabloyu sorgulamak için dizin kullanmadığını ve neden tüm birincil anahtar dizinini engellediğini hayal edebildiğim tek nedenini gösterir.

Basitleştirilmiş dava

Birkaç kayıt içeren yalnızca iki tablo olduğunda da sorun daha basit bir durumda yeniden oluşturulabilir, ancak alt tablonun üst tablo ref sütununda bir dizini yoktur.

parentTablo oluştur

CREATE TABLE `parent` (
  `id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

childTablo oluştur

CREATE TABLE `child` (
  `id` int(10) unsigned NOT NULL,
  `parent_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

Tabloları doldur

INSERT INTO `parent` (id) VALUES (1), (2);
INSERT INTO `child` (id, parent_id) VALUES (1, NULL), (2, NULL);

İki paralel işlemle test edin:

  • İşlem 1

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 1;
  • İşlem 2

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 2;

Her iki durumda da ortak nokta, MySQL'in indeks kullanmamasıdır. Tüm masanın kilitlenmesinin nedeni olduğuna inanıyorum.

Bizim çözümümüz

Şimdilik görebildiğimiz tek çözüm, ipliğin temizlenmesini sağlamak için varsayılan kilit bekleme zaman aşımını 50 saniyeden 500 saniyeye çıkarmak. Sonra parmaklarınızı çapraz tutun.

Herhangi bir yardım takdir.


Bir sorum var: İşlemlerden herhangi birinde COMMIT yürüttünüz mü?
RolandoMySQLDBA

Elbette. Sorun, diğer tüm işlemlerin biri değişiklik yapana kadar beklemesi gerektiğidir. Basit test durumu, sorunun nasıl yeniden oluşturulacağını gösteren bir taahhüt ifadesi içermez. Beklemeyen işlemde kesinleştirme veya geri alma işlemi yaparsanız, kilidi aynı anda serbest bırakır ve bekleyen işlem işlemi tamamlar.
vitalidze

MySQL'in her iki durumda da indeks kullanmadığını söylediğinizde, gerçek senaryoda hiçbiri olmadığı için mi? Eğer indeksler varsa onlar için kod verebilir misiniz? Aşağıda belirtilen dizin önerilerinden herhangi birini denemek mümkün mü? Dizin yoksa ve herhangi bir dizin eklemeyi denemek mümkün değilse, MySQL her iş parçacığı tarafından işlenen veri kümesini kısıtlayamaz. Bu durumda, N iş parçacığı sunucu iş yükünü N katıyla çarpar ve yalnızca bir iş parçacığının {WHERE vd.ivehicle_id IN (2, 13) AND dp.dirty_data = gibi bir parametre listesiyle çalışmasına izin vermek daha verimli olur 1;}.
JM Hicks

Tamam, bağlı örnek veri dosyasında sıkışmış dizinleri bulundu.
JM Hicks

birkaç soru daha: 1) day_positionzaman aşımı sınırını 500 saniyeye çıkarmanız gerekecek kadar yavaş çalışmaya başladığında, tablo normalde kaç satır içerir? 2) Yalnızca örnek verilere sahip olduğunuzda çalışması ne kadar sürer?
JM Hicks

Yanıtlar:


3

YENİ YANIT (MySQL tarzı dinamik SQL): Tamam, bu problemi diğer posterin tarif ettiği şekilde ele alıyor - karşılıklı uyumsuz özel kilitlerin edinilme sırasını tersine çeviriyor, böylece kaç tane meydana gelirse gelsin, sadece işlemin sonunda en az süre.

Bu, ifadenin okunan kısmını kendi select deyimine ayırarak ve deyim görünümü sırası nedeniyle en son çalıştırılmaya zorlanacak ve yalnızca proc_warnings tablosunu etkileyecek bir delete deyimi oluşturarak gerçekleştirilir.

Sql kemanında bir demo var:

Bu bağlantı , örnek verileri içeren şemayı ve eşleşen satırlar için basit bir sorguyu gösterir ivehicle_id=2. Hiçbiri silinmediği için 2 satır oluşur.

Bu bağlantı aynı şemayı, örnek verileri gösterir, ancak SP'ye proc_warningsgirişleri silmesini söyleyen DeleteEntries saklanan programına bir değer 2 iletir ivehicle_id=2. Satırların basit sorgusu, tümü başarıyla silindiği için hiçbir sonuç döndürmez. Demo bağlantıları yalnızca kodun silinmek üzere çalıştığını gösterir. Uygun test ortamına sahip kullanıcı, bunun engellenen iş parçacığının sorununu çözüp çözmediği konusunda yorum yapabilir.

Kolaylık sağlamak için kod da şöyledir:

CREATE PROCEDURE DeleteEntries (input_vid INT)
BEGIN

    SELECT @idstring:= '';
    SELECT @idnum:= 0;
    SELECT @del_stmt:= '';

    SELECT @idnum:= @idnum+1 idnum_col, @idstring:= CONCAT(@idstring, CASE WHEN CHARACTER_LENGTH(@idstring) > 0 THEN ',' ELSE '' END, CAST(id AS CHAR(10))) idstring_col
    FROM proc_warnings
    WHERE EXISTS (
        SELECT 0
        FROM day_position
        WHERE day_position.transaction_id = proc_warnings.transaction_id
        AND day_position.dirty_data = 1
        AND EXISTS (
            SELECT 0
            FROM ivehicle_days
            WHERE ivehicle_days.id = day_position.ivehicle_day_id
            AND ivehicle_days.ivehicle_id = input_vid
        )
    )
    ORDER BY idnum_col DESC
    LIMIT 1;

    IF (@idnum > 0) THEN
        SELECT @del_stmt:= CONCAT('DELETE FROM proc_warnings WHERE id IN (', @idstring, ');');

        PREPARE del_stmt_hndl FROM @del_stmt;
        EXECUTE del_stmt_hndl;
        DEALLOCATE PREPARE del_stmt_hndl;
    END IF;
END;

Bu, bir işlemin içinden programı çağıracağınız sözdizimidir:

CALL DeleteEntries(2);

ORİJİNAL CEVAP (hala çok perişan olmadığını düşünüyorum) 2 konuya benziyor: 1) yavaş sorgu 2) beklenmedik kilitleme davranışı

1 numaralı sorunla ilgili olarak, yavaş sorgular, ardışık sorgu deyiminin basitleştirilmesi ve dizinlere yararlı eklemeleri veya değiştirmelerde aynı iki teknikle çözülür. Siz zaten endekslerle bağlantı kurdunuz - onlar olmadan optimize edici işlemek için sınırlı bir satır kümesi arayamaz ve her bir tablodaki her satır fazladan satır başına çarpılarak yapılması gereken ekstra iş miktarını tarar.

ŞEMA VE ENDEKSLER SONRASI GÖRÜLDÜKTEN SONRA REVİZE EDİLDİ: Ancak, iyi bir dizin yapılandırmanız olduğundan emin olarak sorgunuz için en yüksek performans avantajını elde edeceğinizi düşünüyorum. Bunu yapmak için, daha büyük dizinlerin takas edilmesi ve ek dizin yapısının eklendiği aynı tablolarda kayda değer ölçüde daha yavaş kesici uç performansı ile daha iyi silme performansı ve muhtemelen daha iyi silme performansı için gidebilirsiniz.

BAZI DAHA İYİ:

CREATE TABLE  `day_position` (
    ...,
    KEY `day_position__id_rvrsd` (`dirty_data`, `ivehicle_day_id`)

) ;


CREATE TABLE  `ivehicle_days` (
    ...,
    KEY `ivehicle_days__vid_no_sort_index` (`ivehicle_id`)
);

BURADA REVİZE ÇOK: Çalıştırdığı sürece sürdüğü için, dirty_data'yı dizinde bırakacağım ve ivehicle_day_id'den sonra dizin sırasına yerleştirdiğimde de yanlış anladım - ilk olmalı.

Ama eğer elimde olsaydı, bu noktada, bu kadar uzun sürmesi için iyi miktarda veri olması gerektiğinden, sadece en iyi indekslemeyi aldığımdan emin olmak için tüm kaplama dizinlerini seçerdim. Sorunun bir kısmını ekarte edecek başka bir şey yoksa sorun giderme sürem satın alınabilir.

EN İYİ / KAPLAMA ENDEKSLERİ:

CREATE TABLE  `day_position` (
    ...,
    KEY `day_position__id_rvrsd_trnsid_cvrng` (`dirty_data`, `ivehicle_day_id`, `transaction_id`)
) ;

CREATE TABLE  `ivehicle_days` (
    ...,
    UNIQUE KEY `ivehicle_days__vid_id_cvrng` (ivehicle_id, id)
);

CREATE TABLE  `proc_warnings` (

    .., /*rename primary key*/
    CONSTRAINT pk_proc_warnings PRIMARY KEY (id),
    UNIQUE KEY `proc_warnings__transaction_id_id_cvrng` (`transaction_id`, `id`)
);

Son iki değişiklik önerisi tarafından aranan iki performans optimizasyonu hedefi vardır:
1) Art arda erişilen tablolar için arama anahtarları, şu anda erişilen tablo için döndürülen kümelenmiş anahtar sonuçlarla aynı değilse, yapılması gerekenleri ortadan kaldırırız kümelenmiş dizindeki ikinci bir tarama ile dizin arama-arama işlemi kümesi
ikinci bir 2) Eğer ikincisi durum böyle değilse, en azından optimize edicinin daha verimli bir birleştirme algoritması seçmesi olasılığı vardır, çünkü dizinler gerekli birleştirme anahtarları sıralı olarak.

Sorgunuz olabildiğince basitleştirilmiş gibi görünüyor (daha sonra düzenlenmesi durumunda buraya kopyalanır):

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
    ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
    ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;

Elbette, sorgu birleştiricinin ilerleyiş biçimini etkileyen yazılı birleştirme sırası hakkında bir şey olmadığı sürece, bu durumda, başkalarının sağladığı yeniden yazma önerilerinden bazılarını deneyebilirsiniz (belki de w / index ipuçları dahil) (isteğe bağlı):

DELETE FROM proc_warnings
FORCE INDEX (`proc_warnings__transaction_id_id_cvrng`, `pk_proc_warnings`)
WHERE EXISTS (
    SELECT 0
    FROM day_position
    FORCE INDEX (`day_position__id_rvrsd_trnsid_cvrng`)  
    WHERE day_position.transaction_id = proc_warnings.transaction_id
    AND day_position.dirty_data = 1
    AND EXISTS (
        SELECT 0
        FROM ivehicle_days
        FORCE INDEX (`ivehicle_days__vid_id_cvrng`)  
        WHERE ivehicle_days.id = day_position.ivehicle_day_id
        AND ivehicle_days.ivehicle_id = ?
    )
);

# 2 ile ilgili olarak, beklenmedik kilitleme davranışı.

Her iki sorgu göre birincil anahtar = 53 ile bir satırda özel bir X kilidi istiyor. Ancak, hiçbiri proc_warnings tablosundan satırları silmek gerekir. Endeksin neden kilitlendiğini anlamıyorum.

Kilitlenecek veri satırı kümelenmiş bir dizinde olduğu için kilitli dizin olurdu, yani tek bir veri satırı dizinde bulunduğu sanırım.

Kilitlenecektir, çünkü:
1) http://dev.mysql.com/doc/refman/5.1/en/innodb-locks-set.html uyarınca

... bir DELETE genellikle SQL deyiminin işlenmesinde taranan her dizin kaydında kayıt kilitlerini ayarlar. İfadede satırı hariç tutacak WHERE koşullarının olup olmadığı önemli değildir. InnoDB tam WHERE koşulunu hatırlamaz, sadece hangi indeks aralıklarının tarandığını bilir.

Yukarıda da bahsettiniz:

... bana göre OKUMA TAAHHÜT un ana özelliği kilitlerle nasıl başa çıktığıdır. Eşleşmeyen satırların dizin kilitlerini serbest bırakmalıdır, ancak açmaz.

ve bunun için aşağıdaki referansı sağladı:
http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-committed

Sizinle aynı olanı belirtir, ancak aynı referansa göre bir kilidin açılması gereken bir koşul vardır:

Ayrıca, MySQL WHERE koşulunu değerlendirdikten sonra eşleşmeyen satırlar için kayıt kilitleri serbest bırakılır.

Bu kılavuz sayfasında da tekrarlananlar http://dev.mysql.com/doc/refman/5.1/en/innodb-record-level-locks.html

READ COMMITTED yalıtım düzeyini kullanmanın veya innodb_locks_unsafe_for_binlog'u etkinleştirmenin başka etkileri de vardır: Eşleşmeyen satırlar için kayıt kilitleri MySQL WHERE koşulunu değerlendirdikten sonra serbest bırakılır.

Bu nedenle, kilidin yeniden açılabilmesi için WHERE koşulunun değerlendirilmesi gerektiği söylendi. Ne yazık ki, WHERE koşulunun ne zaman değerlendirildiği söylenmez ve muhtemelen bir plandan diğerine optimize edici tarafından oluşturulan bir şey olabilir. Ancak bize, kilit sürümünün, bir şekilde sorgu yürütme performansına bağlı olduğunu, optimizasyonun yukarıda tartıştığımız gibi ifadenin dikkatli bir şekilde yazılmasına ve dizinlerin mantıklı kullanımına bağlı olduğunu söylüyor. Daha iyi masa tasarımı ile de geliştirilebilir, ancak bu muhtemelen en iyi şekilde ayrı bir soruya bırakılacaktır.

Ayrıca, proc_warnings tablosu boş olduğunda dizin kilitlenmez

Veritabanı, dizin içinde kayıtları kilitleyemez.

Dahası, dizin ... day_position tablosu daha az sayıda satır (yani yüz satır) içerdiğinde kilitlenmez.

Bu, bunlarla sınırlı olmamakla birlikte sayısız şey anlamına gelebilir: istatistiklerdeki bir değişiklik nedeniyle farklı bir yürütme planı, çok daha küçük bir veri kümesi nedeniyle çok daha hızlı bir yürütme nedeniyle gözlemlenecek çok kısa bir kilit / birleştirme işlemi.


WHERESorgu tamamladığında durum değerlendirilir. Öyle değil mi? Bazı eşzamanlı sorgular yürütüldükten hemen sonra kilidin serbest bırakıldığını düşündüm. Doğal davranış budur. Ancak bu gerçekleşmez. Bu iş parçacığında önerilen sorguların hiçbiri proc_warningstabloda kümelenmiş dizin kilitlemesini önlemeye yardımcı olur . Sanırım MySQL'e bir hata vereceğim. Yardım ettiğin için teşekkür ederim.
vitalidze

Ben de kilitleme davranışını önlemek için beklemem. Ben kilitlemek için beklenir çünkü belgelerin beklediğimizi söylüyor, sorguyu işlemek için bu şekilde olsun ya da olmasın diyor. Ben sadece performans sorunu kurtulmak eşzamanlı sorgu böyle açık bir şekilde (500 + saniye zaman aşımı) uzun bir süre için engel olmasını engelleyecektir.
JM Hicks

{WHERE} öğeniz, birleştirme işlemi sırasında birleştirme hesaplamasına dahil edilecek satırları kısıtlamak için kullanılabilecek gibi görünse de, {WHERE} yan tümceniz, tüm birleştirme kümesi tamamlanana kadar kilitli satır başına nasıl değerlendirilebileceğini görmüyorum de hesapladı. Bununla birlikte, analizimiz için, "Sorgu tamamlandığında NEREDE koşulu değerlendirilir" olduğundan şüphelenmemiz gerektiğinden şüpheleniyorum. Yine de bu, beni aynı genel sonuca götürüyor, performansın çözülmesi gerekiyor ve sonra görünen eşzamanlılık derecesi orantılı olarak artacak.
JM Hicks

Uygun dizinlerin, proc_warnings tablosunda gerçekleşen tam tablo taramasını potansiyel olarak ortadan kaldırabileceğini unutmayın. Bunun olabilmesi için, bizim için iyi çalışabilmesi için sorgu optimize ediciye ihtiyacımız var ve onunla güzel bir şekilde çalışmak için dizinlerimize, sorguya ve verilerimize ihtiyacımız var. Parametre değerleri, hedef tablodaki iki sorgu arasında örtüşmeyen satırların sonunda değerlendirilmelidir. Dizinler, sorgu optimize ediciye bu satırları verimli bir şekilde aramak için bir araç sağlamalıdır. Potansiyel arama verimliliğini anlamak ve böyle bir plan seçmek için optimize ediciye ihtiyacımız var.
JM Hicks

Her şey parametre değerleri, dizinler, proc_warnings tablosunda örtüşmeyen sonuçlar ve optimizer planı seçimi arasında iyi giderse, her iş parçacığının sorguyu yürütmek için gereken süre boyunca kilitler oluşturulabilse de, bu kilitler örtüşüyorsa, diğer iş parçacıklarının kilit istekleriyle çakışmaz.
JM Hicks

3

READ_COMMITTED'in bu duruma nasıl neden olabileceğini görebiliyorum .

READ_COMMITTED üç şeye izin verir:

  • READ_COMMITTED yalıtım düzeyini kullanarak diğer işlemlerin gerçekleştirdiği değişikliklerin görünürlüğü .
  • Tekrarlanamayan Okumalar: Her seferinde farklı bir sonuç alma olasılığı ile aynı alma işlemini gerçekleştiren işlem.
  • Hayaletler: İşlemlerin önceden görünmediği yerlerde satırları olabilir.

Bu işlemin kendisi için dahili bir paradigma oluşturur, çünkü işlem aşağıdakilerle teması sürdürmelidir:

  • InnoDB Tampon Havuzu (taahhüt hala boşken)
  • Tablonun Birincil Anahtarı
  • belki
    • Çift Yazma Arabelleği
    • Tablo Alanını Geri Al
  • Resimsel gösterim

İki farklı READ_COMMITTED işlemi aynı şekilde güncellenen aynı tablolara / satırlara erişiyorsa, bir tablo kilidi değil, gen_clust_index ( özel olarak Kümelenmiş Dizin) içinde özel bir kilit beklemeye hazır olun . Basitleştirilmiş durumunuzdaki sorgular göz önüne alındığında:

  • İşlem 1

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 1;
  • İşlem 2

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 2;

Aynı konumu gen_clust_index içinde kilitliyorsunuz. Birisi "her işlemin farklı bir birincil anahtarı vardır" diyebilir. Ne yazık ki, bu InnoDB'nin gözünde böyle değil. Aynı şekilde, kimlik 1 ve kimlik 2 aynı sayfada yer alır.

information_schema.innodb_locksSoruda verilen geri dön

| lock_id                | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |

Hariç olmak üzere lock_id, lock_trx_idkilit açıklama geri kalanı aynıdır. İşlemler aynı seviyede oyun alanında olduğu için (aynı işlem izolasyonu), bu gerçekten gerçekleşmelidir .

İnan bana, daha önce bu tür bir duruma değindim. İşte bu konuda son mesajlar:


MySQL belgelerinde açıkladığınız şeyleri okudum. Ama bana gelince , OKUYULUĞU OKUYUN ana özelliği kilitlerle nasıl başa çıktığıdır . Eşleşmeyen satırların dizin kilitlerini serbest bırakmalıdır, ancak açmaz.
vitalidze

Bir hata nedeniyle yalnızca tek bir SQL ifadesi geri alınırsa, ifade tarafından ayarlanan bazı kilitler korunabilir. Bir biçimde InnoDB'nin depolar satır kilitleri hangi deyimiyle ayarlanan hangi kilit sonra bilemez öyle ki Bunun nedeni: dev.mysql.com/doc/refman/5.5/en/innodb-deadlock-detection.html
RolandoMySQLDBA

Kilitleme için aynı sayfada iki satır bulunduğundan bahsettiğimi lütfen unutmayın (Bkz. Look back at information_schema.innodb_locks you supplied in the Question)
RolandoMySQLDBA

Tek bir ifadeyi geri alma hakkında - Bunu tek bir işlemde tek bir deyim başarısız olursa yine de kilitleri tutabilir gibi anlıyorum. Bu iyi. Benim büyük sorum neden başarıyla DELETEdeyimi işledikten sonra eşleşen olmayan satır kilitleri yayımlamıyor olmasıdır .
vitalidze

İki tamamlama kilidiyle biri geri döndürülmelidir. Kilitlerin sızması mümkündür. ÇALIŞMA TEORİSİ: Geri alınan işlem yeniden denenebilir ve bu işlemi gerçekleştiren önceki işlemden eski bir kilitle karşılaşabilir.
RolandoMySQLDBA

2

Sorguya ve açıklamaya baktım. Emin değilim, ama bir sorun var, sorun şu. Sorguya bakalım:

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
   ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
   ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=? AND dp.dirty_data=1;

Eşdeğer SELECT:

SELECT pw.id
FROM proc_warnings pw
INNER JOIN day_position dp
   ON dp.transaction_id = pw.transaction_id
INNER JOIN ivehicle_days vd
   ON vd.id = dp.ivehicle_day_id
WHERE vd.ivehicle_id=16 AND dp.dirty_data=1;

Açıklamasına bakarsanız, yürütme planının proc_warnings tabloyla . Bu, MySQL'in tablodaki birincil anahtarı taradığı ve her satır için koşulun doğru olup olmadığını kontrol eder ve eğer öyleyse - satır silinir. Yani MySQL birincil anahtarı kilitlemek zorunda.

İhtiyacınız olan şey JOIN siparişini tersine çevirmek, yani tüm işlem kimliklerini bulmak vd.ivehicle_id=16 AND dp.dirty_data=1 ve proc_warningsmasaya katılmak .

Yani endekslerden birini düzeltmeniz gerekecek:

ALTER TABLE `day_position`
 DROP INDEX `day_position__id`,
 ADD INDEX `day_position__id`
   USING BTREE (`ivehicle_day_id`, `dirty_data`, `transaction_id`);

ve silme sorgusunu yeniden yazın:

DELETE pw
FROM (
  SELECT DISTINCT dp.transaction_id
  FROM ivehicle_days vd
  JOIN day_position dp ON dp.ivehicle_day_id = vd.id
  WHERE vd.ivehicle_id=? AND dp.dirty_data=1
) as tr_id
JOIN proc_warnings pw ON pw.transaction_id = tr_id.transaction_id;

Ne yazık ki bu yardımcı olmuyor, yani satırlar proc_warningshala kilitleniyor. Yine de teşekkürler.
vitalidze

2

İşlem düzeyini istediğiniz şekilde ayarladığınızda, yalnızca bir sonraki işleme Taahhüt Edilen Okuma işlemini uygular (böylece otomatik taahhüdü ayarlayın). Bu, autocommit = 0'dan sonra, artık Okuma Taahhütünde değilsiniz demektir. Bu şekilde yazardım:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
DELETE c FROM child c
INNER JOIN parent p ON
    p.id = c.parent_id
WHERE p.id = 1;

Sorgulayarak hangi yalıtım düzeyinde olduğunuzu kontrol edebilirsiniz.

SELECT @@tx_isolation;

Bu doğru değil. Neden bir SET AUTOCOMMIT=0sonraki işlem için yalıtım seviyesini sıfırlamalı? Daha önce başlatılmamışsa yeni bir işlem başlattığına inanıyorum (bu benim durumum). Bu nedenle, daha kesin olmak gerekirse bir sonraki START TRANSACTIONya da BEGINifade gerekli değildir. Otomatik taahhüdü devre dışı bırakma amacım, DELETEdeyim yürütüldükten sonra işlemi açık bırakmak .
vitalidze

1
@SqlKiwi bu gönderiyi düzenlemenin yoluydu ve bu ;-)
jcolebrand hakkında
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.