MariaDB'de bir transaction START TRANSACTION (veya BEGIN) ile başlar, COMMIT ile kalıcı hâle gelir ya da ROLLBACK ile tamamen geri alınır. Varsayılan olarak autocommit açıktır, yani her değiştirici ifade kendi başına bir transaction olarak hemen kalıcılaşır. Birden fazla ifadenin "ya hepsi ya hiçbiri" mantığıyla çalışmasını istiyorsanız onları açık bir transaction içine almanız gerekir. InnoDB'nin varsayılan izolasyon seviyesi REPEATABLE READ'dir.

Bu yazıda MariaDB 12.3 LTS dönemine ait resmî belgelere göre transaction ifadelerini, örtük commit tuzaklarını, SAVEPOINT kullanımını, dört izolasyon seviyesinin InnoDB'deki davranışını, innodb_snapshot_isolation değişkenini, satır kilitlerini ve kilitlenme (deadlock) ile kilit bekleme hatalarını nasıl ele alacağınızı anlatıyoruz. Bekleyen kilitleri canlı izlemek için MariaDB SHOW PROCESSLIST ve KILL yazısını tamamlayıcı olarak okuyabilirsiniz.

Temel ifadeler: START TRANSACTION, COMMIT, ROLLBACK

Temel ifadeler: START TRANSACTION, COMMIT, ROLLBACK

Belgedeki sözdizimi bu dört ilişkili ifadeyi birlikte gösterir:

sql
START TRANSACTION [transaction_property [, transaction_property] ...] | BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET autocommit = {0 | 1}

transaction_property:
    WITH CONSISTENT SNAPSHOT
  | READ WRITE
  | READ ONLY

COMMIT transaction'ı bitirir, değişiklikleri sonraki transaction'lara görünür kılar ve transaction'ın aldığı metadata kilitlerini bırakır. ROLLBACK ise yapılan tüm veri değişikliklerini yok eder. WORK kelimesi yalnızca süstür, anlamı değiştirmez. Belgedeki örnek bir toplamı hesaplayıp başka bir tabloya yazan iki ifadeyi tek bir transaction içinde birleştirir:

sql
START TRANSACTION;
SELECT @A:=SUM(salary) FROM table1 WHERE type=1;
UPDATE table2 SET summary=@A WHERE type=1;
COMMIT;

Daha tanıdık bir senaryo para transferidir. İki hesabın bakiyesi aynı anda değişmeli; biri başarılı olup diğeri olmazsa veri tutarsızlaşır. Aşağıdaki küçük örnekte satırlar önce FOR UPDATE ile kilitlenir, sonra güncellenir:

sql
START TRANSACTION;
SELECT bakiye FROM hesaplar WHERE id IN (1, 2) FOR UPDATE;
UPDATE hesaplar SET bakiye = bakiye - 100 WHERE id = 1;
UPDATE hesaplar SET bakiye = bakiye + 100 WHERE id = 2;
COMMIT;

Uygulama ikinci UPDATE öncesinde bir sorun fark ederse COMMIT yerine ROLLBACK çalıştırır ve ilk güncelleme de geri alınır. UPDATE ifadelerinin genel kullanımı için MariaDB INSERT, UPDATE, DELETE yazısına bakın.

autocommit davranışı

autocommit değişkeninin varsayılan değeri 1'dir. Bu modda her sorgu hemen kalıcılaşır ve belgeye göre LOCK IN SHARE MODE ile FOR UPDATE yan tümcelerinin etkisi olmaz. SET autocommit=0; çalıştırdığınızda InnoDB gibi transaction destekleyen tablolardaki değişiklikler siz COMMIT diyene kadar kalıcı olmaz. Bu modda açık bir COMMIT ile bitmeyen transaction'lar örtük olarak geri alınır ve değişiklikler kaybolur. Yalnızca tek bir ifade dizisi için autocommit'i devre dışı bırakmak istiyorsanız START TRANSACTION kullanmak daha temizdir. Ayrıca hem START TRANSACTION hem de SET autocommit = 1, açık bir transaction varsa onu örtük olarak commit eder.

Oturumun o anda bir transaction içinde olup olmadığını in_transaction değişkeniyle görebilirsiniz; bu salt okunur oturum değişkeni transaction içindeyken 1, değilken 0 döndürür.

Örtük commit: geri alınamayan ifadeler

En çok sürpriz yaratan konu budur. Belgeye göre DDL ifadeleri (CREATE, ALTER, DROP), FLUSH, RESET, OPTIMIZE, ANALYZE, CHECK, REPAIR, CACHE INDEX gibi yönetim ifadeleri, BEGIN ve START TRANSACTION ile LOAD DATA INFILE örtük bir COMMIT yapar ve yeni bir transaction başlatır. ROLLBACK sayfası şu örneği verir:

sql
INSERT INTO Table_2 VALUES(5); 
DROP TABLE Table_3 CASCADE; 
ROLLBACK;

Sonuç olarak hem INSERT hem de DROP ayrı transaction'lar olarak gerçekleşir ve ROLLBACK'in hiçbir etkisi olmaz. Sayfa aynı nedenle GRANT ve REVOKE ifadelerinin de çalıştırıldıkları anda örtük commit yaptığını belirtir. Geçici tablolar istisnadır: onları transaction içinde commit tetiklemeden oluşturabilir, değiştirebilir ve silebilirsiniz, ancak bu işlemler geri alınamaz. Tam liste için belgedeki "SQL statements Causing an Implicit Commit" sayfasına bakın.

İki kısıtlama daha vardır: transaction'lar saklı fonksiyonlarda ve tetikleyicilerde kullanılamaz; saklı yordamlarda ve event'lerde ise BEGIN blok başlangıcı anlamına geldiği için transaction başlatmak üzere START TRANSACTION yazmalısınız. Yordamlar için MariaDB stored procedure ve function yazısına bakabilirsiniz.

SAVEPOINT ile kısmi geri alma

InnoDB SAVEPOINT, ROLLBACK TO SAVEPOINT ve RELEASE SAVEPOINT ifadelerini destekler. Savepoint, adlandırılmış bir alt transaction'dır:

sql
SAVEPOINT identifier
ROLLBACK [WORK] TO [SAVEPOINT] identifier
RELEASE SAVEPOINT identifier

ROLLBACK TO, belirtilen savepoint'ten sonra yapılan değişiklikleri geri alır ve sonraki tüm savepoint'leri siler. Dikkat edilmesi gereken nokta şudur: savepoint'ten sonra alınan kilitler bırakılmaz, geri alma işleminden sonra da devam eder. RELEASE SAVEPOINT hiçbir değişikliği geri almaz veya kalıcılaştırmaz, yalnızca savepoint'i kaldırır. COMMIT (örtük olanlar dahil) veya TO içermeyen bir ROLLBACK tüm transaction üzerinde çalışır ve tüm savepoint'leri siler.

sql
START TRANSACTION;
INSERT INTO siparisler (musteri_id, toplam) VALUES (7, 250);
SAVEPOINT sp_kalemler;
INSERT INTO siparis_kalemleri (siparis_id, urun_id) VALUES (LAST_INSERT_ID(), 99);
ROLLBACK TO SAVEPOINT sp_kalemler;
COMMIT;

Bu örnekte yalnızca kalem eklemesi geri alınır, sipariş satırı commit ile kalıcılaşır. Belgede belirtilen hata davranışları da önemlidir: transaction başlatılmadan SAVEPOINT çalıştırılırsa hata verilmez ama savepoint de oluşturulmaz. Var olmayan bir savepoint'e ROLLBACK TO veya RELEASE denirse şu hata gelir:

sql
ERROR 1305 (42000): SAVEPOINT svp_name does not exist

AND CHAIN, RELEASE ve erişim modu

COMMIT AND CHAIN ve ROLLBACK AND CHAIN, mevcut transaction biter bitmez aynı izolasyon seviyesi ve erişim moduna sahip yenisini başlatır. RELEASE ise transaction bittikten sonra istemci oturumunu kapatır. Varsayılan davranışı completion_type sistem değişkeni belirler; NO CHAIN ve NO RELEASE bu varsayılanı geçersiz kılar. START TRANSACTION READ ONLY ile başlatılan transaction veri yazamaz ve depolama motoru yazma yapan transaction'larda kullanılamayan iyileştirmeler uygulayabilir. Aynı ifadede hem READ WRITE hem READ ONLY verilemez.

İzolasyon seviyeleri

İzolasyon seviyeleri: REPEATABLE READ ve innodb_snapshot_isolation, READ COMMITTED ne zaman seçilir?

İzolasyon seviyesi, eşzamanlı transaction'ların birbirinin değişikliklerini ne zaman göreceğini belirler. SET TRANSACTION ile ayarlanır:

sql
SET [GLOBAL | SESSION] TRANSACTION
    transaction_property [, transaction_property] ...

transaction_property:
    ISOLATION LEVEL level
  | READ WRITE
  | READ ONLY

level:
     REPEATABLE READ
   | READ COMMITTED
   | READ UNCOMMITTED
   | SERIALIZABLE

Kapsam kuralı şöyledir: GLOBAL sonraki tüm yeni oturumları etkiler, mevcut oturumlar değişmez ve SUPER yetkisi gerekir. SESSION mevcut oturumdaki sonraki tüm transaction'ları etkiler. Anahtar kelime yazılmazsa ayar yalnızca bir sonraki, henüz başlamamış transaction için geçerlidir. Bu son biçimi açık bir transaction içinde kullanmak hata verir:

sql
START TRANSACTION;

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
ERROR 1568 (25001): Transaction characteristics can't be changed while a transaction is in progress

Sunucu başlarken genel varsayılanı ayarlamak için seçenek dosyasında tire kullanılan değerler yazılır:

ini
[mariadb]
transaction-isolation = REPEATABLE-READ

Çalışma anındaki değeri görmek için MariaDB 11.1'den itibaren transaction_isolation değişkeni kullanılır, daha eski sürümlerde tx_isolation. Seçenek dosyalarının nerede okunduğu için MariaDB my.cnf yapılandırma dosyaları yazısına bakın.

SeviyeTutarlı okumaGap kilidiNot
READ UNCOMMITTEDKilitsiz, satırın eski bir sürümü okunabilir (dirty read)YokDiğer açılardan READ COMMITTED gibi
READ COMMITTEDHer ifade kendi taze görüntüsünü alırYok (yinelenen anahtar denetimi hariç)STATEMENT binlog formatıyla yazma reddedilir
REPEATABLE READTüm ifadeler ilk okumada alınan görüntüyü görürVarInnoDB varsayılanı
SERIALIZABLEautocommit kapalıyken düz SELECT, LOCK IN SHARE MODE olurVarDağıtık XA transaction için önerilir

REPEATABLE READ ve innodb_snapshot_isolation

Varsayılan seviyede aynı transaction içindeki tüm kilitsiz SELECT ifadeleri, ilk okumada oluşturulan aynı görüntüyü okur; böylece birbirleriyle tutarlıdır. Kilitli okumalar, UPDATE ve DELETE için ise kilitleme, aramanın benzersiz bir indexte benzersiz koşulla mı yoksa aralık koşuluyla mı yapıldığına bağlıdır; belge MariaDB'nin benzersiz indexler için gap kilitlemesini gevşetmediğini belirtir.

innodb_snapshot_isolation değişkeni bu seviyeyi daha katı uygular. Açıkken bir UPDATE veya DELETE, transaction'ın görüntüsü alındıktan sonra başka bir transaction tarafından değiştirilmiş bir satırı değiştirmeye çalışırsa reddedilir ve ER_CHECKREAD (1020) hatası gelir. Bu hata bir deadlock gibi ele alınır: yalnızca ifade değil bütün transaction geri alınır. Belgeye göre değişken MariaDB 11.6.2'den itibaren varsayılan olarak açıktır; onu ilk tanıtan 10.11 ve 11.4 uzun vadeli sürüm serilerinde ise varsayılan kapalıdır. Belge bu nedenle değeri açıkça ayarlamanızı ve ON tercih etmenizi önerir; deadlock'ları zaten yeniden deneyen bir uygulama ek değişiklik gerektirmez. Eski sürümden 12.3'e geçerken bu fark davranışı değiştirebilir, ayrıntılar için MariaDB sürüm yükseltme yazısına bakın.

READ COMMITTED ne zaman seçilir?

Bu seviyede InnoDB gap kilidi almaz; kilitli okumalar ile UPDATE ve DELETE yalnızca incelenen index kayıtlarını kilitler ve kilitli kayıtların yanına yeni satır eklenmesine izin verir. Bunun bir yan etkisi vardır: ikili günlük açık ve binlog_format STATEMENT iken satır yazan her ifade 1665 hatasıyla reddedilir; varsayılan MIXED ve ROW formatları etkilenmez. Binlog formatları için MariaDB binary log ve PITR yazısına bakın.

Belgenin "Choosing an Isolation Level" bölümü yaygın bir yanlış inanışı düzeltir: READ COMMITTED her iki yönde de bir verim ayarı değildir. Bu seviyede her ifade kendi okuma görünümünü açar ve yüksek eşzamanlılıkta çok sayıda kısa ifade içeren iş yüklerinde bu maliyet ölçülebilir hâle gelir. Uzun süren transaction'lardaki karmaşık ifadeler ise daha ucuz olabilir. Belgenin tavsiyesi uygulama farklı bir seviyenin anlamına ihtiyaç duymuyorsa varsayılan REPEATABLE READ'de kalmak ve yön varsaymak yerine iş yükünü ölçmektir.

InnoDB kilit modları

Kilitler eşzamanlı yazma işlemlerinin çakışmasını önler. İki standart satır kilidi vardır: paylaşılan kilit (S) satırı okumak için alınır ve başkalarının okumasına izin verir ama yazmasına izin vermez; dışlayıcı kilit (X) satıra yazmak için alınır ve başka transaction'ların aynı satırı kilitlemesini engeller. Tablo ve satır düzeyinde kilitlerin birlikte var olabilmesi için niyet kilitleri (IS ve IX) bulunur. Varsayılan seviyede InnoDB ayrıca gap kilidi kullanır: bir index kaydını kilitlerken önceki kayıtla arasındaki boşluğu da kilitler, böylece başka bir transaction o boşluğa yeni kayıt ekleyemez. Benzersiz bir indexte yinelenen anahtar denetimi her seviyede next-key kilidi alır.

Belgenin vurguladığı ince bir nokta, InnoDB'nin kilitleri mantıksal satırlara değil index kayıtlarına koymasıdır. İkincil bir indexi kapsayan (covering) bir okuma yalnızca o indexin kayıtlarını kilitler; aynı satırın o indexte olmayan bir sütununu değiştiren başka bir transaction birincil index kaydını kilitlediği için ikisi birbirini engellemeyebilir. Bu yüzden indexler yalnızca hızı değil kilit davranışını da etkiler; MariaDB index oluşturma yazısında bu ilişkiye değiniyoruz.

Kilitli okuma: FOR UPDATE, LOCK IN SHARE MODE, NOWAIT, SKIP LOCKED

SELECT ... FOR UPDATE okunan satırları, başkalarının yazmasını ve kilitlemesini engelleyecek şekilde kilitler; LOCK IN SHARE MODE paylaşılan kilit alır. İkisi de yalnızca autocommit kapalıyken veya ifade bir transaction içindeyken anlam taşır ve kilitler transaction commit edildiğinde bırakılır. WAIT n ile en fazla n saniye beklenir, NOWAIT ile kilit alınamazsa ifade hemen başarısız olur; WAIT 0, NOWAIT ile eşdeğerdir. SKIP LOCKED ise kilitlenemeyen satırları sonuçtan çıkarır, açık bir NOWAIT içerir ve yalnızca InnoDB'de uygulanır. Bir iş kuyruğundan bekleyen işleri birden çok işçinin çakışmadan alması bu yan tümcenin tipik kullanımıdır:

sql
START TRANSACTION;
SELECT id FROM is_kuyrugu WHERE durum = 'bekliyor'
ORDER BY id LIMIT 10
FOR UPDATE SKIP LOCKED;
-- seçilen işleri işle ve durumlarını güncelle
COMMIT;

Deadlock ve kilit bekleme hataları

Deadlock ve kilit bekleme hataları: 1213 ER_LOCK_DEADLOCK, 1205 ER_LOCK_WAIT_TIMEOUT

İki hata kodu transaction'larla çalışırken mutlaka karşınıza çıkar:

HataMesajNe geri alınır
1213 ER_LOCK_DEADLOCKDeadlock found when trying to get lock; try restarting transactionBütün transaction
1205 ER_LOCK_WAIT_TIMEOUTLock wait timeout exceeded; try restarting transactionVarsayılan olarak yalnızca son ifade

Deadlock, iki transaction'ın birbirinin tuttuğu kilidi beklemesidir; örneğin biri önce 1. sonra 2. satırı, diğeri önce 2. sonra 1. satırı güncellerken. InnoDB bunu hemen fark eder, transaction'lardan birini geri alır ve ona 1213 hatasını döndürür. Belge bunun bir sunucu arızası olmadığını, eşzamanlı transaction çalıştıran her uygulamada olabileceğini belirtir. Normal çözüm bütün transaction'ı yeniden denemektir. Olasılığı azaltmak için tablo ve satırlara tüm transaction'larda aynı sırayla erişin, transaction'ları kısa tutun, ifadelerin daha az satır kilitlemesi için index ekleyin ve gereken tüm satırları başta FOR UPDATE ile kilitleyin. Son deadlock'u görmek için SHOW ENGINE INNODB STATUS çıktısındaki LATEST DETECTED DEADLOCK bölümüne bakın.

1205 hatası ise kilit için tanınan süre dolduğunda gelir. Satır kilitleri için süre innodb_lock_wait_timeout değişkenidir ve varsayılanı 50 saniyedir; metadata kilitleri için lock_wait_timeout kullanılır ve varsayılanı bir gündür. Varsayılan olarak yalnızca zaman aşımına uğrayan ifade geri alınır, transaction önceki değişiklikleriyle açık kalır; bu yüzden onu sizin commit veya rollback etmeniz gerekir. innodb_rollback_on_timeout ayarlanırsa InnoDB bütün transaction'ı geri alır. Kilidi tutan transaction çoğu zaman bir transaction başlatıp hiç commit etmeyen bir oturumdur. Bu tür boşta kalan transaction'lar için idle_transaction_timeout, idle_write_transaction_timeout ve idle_readonly_transaction_timeout değişkenleri belirtilen saniye boyunca boşta kalan transaction'ın bağlantısını kapatır; üçünün de varsayılanı sıfırdır, yani zaman aşımı yoktur. Hata kodlarının çözüm adımları MariaDB sık hatalar ve çözümleri yazısında da özetleniyor.

Sık yapılan hatalar

  • Transaction içinde DDL çalıştırmak: ALTER TABLE veya CREATE INDEX örtük commit yapar; önceki değişiklikler artık geri alınamaz.
  • autocommit=0 bırakıp commit etmemek: açık kalan transaction kilit tutar, diğer oturumlar 1205 hatası alır ve bağlantı koptuğunda değişiklikler geri alınır.
  • Deadlock'u yeniden denememek: 1213 bütün transaction'ı geri alır; uygulama transaction'ı baştan çalıştırmalıdır, yalnızca son ifadeyi değil.
  • 1205 sonrası transaction'ın bittiğini sanmak: varsayılan olarak yalnızca ifade geri alınır; transaction'ı açıkça bitirin.
  • autocommit açıkken FOR UPDATE beklemek: transaction dışında bu yan tümcenin InnoDB'de etkisi yoktur.
  • READ COMMITTED'ı hız ayarı sanmak: belge yön varsaymak yerine ölçmeyi önerir.
  • innodb_snapshot_isolation varsayılanına güvenmek: sürüm serileri arasında farklıdır; değeri açıkça ayarlayın.

Sık Sorulan Sorular

BEGIN ile START TRANSACTION arasında fark var mı?

Normal SQL ifadesi olarak ikisi de yeni transaction başlatır. Fark saklı programlardadır: yordamlarda ve event'lerde BEGIN kullanılamaz, START TRANSACTION yazmanız gerekir.

MyISAM tablolarda transaction çalışır mı?

COMMIT ve ROLLBACK yalnızca transaction destekleyen motorlarda veriyi etkiler. Belge yine de transaction'ın eriştiği her tabloda metadata kilidi aldığını ve bunun MEMORY gibi transaction desteklemeyen motorlarda da geçerli olduğunu, bu yüzden onlarla da transaction kullanmanın anlamlı olabileceğini belirtir.

Geçerli izolasyon seviyemi nasıl görürüm?

MariaDB 11.1'den itibaren SELECT @@GLOBAL.transaction_isolation, @@SESSION.transaction_isolation; ile genel ve oturum değerlerini görebilirsiniz.

WITH CONSISTENT SNAPSHOT ne işe yarar?

Normal START TRANSACTION okuma görünümünü tembel biçimde, yani ilk okuma ifadesinde oluşturur. WITH CONSISTENT SNAPSHOT ise InnoDB için görünümü ifade çalıştığı anda kurar; etkisi, START TRANSACTION ardından herhangi bir InnoDB tablosundan SELECT yapılmasıyla aynıdır. Görüntünün tam olarak transaction başında alınması gereken durumlarda, örneğin tutarlı bir mantıksal yedekte, işe yarar; yedekleme seçenekleri için mariadb-dump ile yedekleme yazısına bakabilirsiniz.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: MariaDB öğrenme rehberi · SHOW PROCESSLIST ve KILL · Sık hatalar · Foreign key ve CHECK · Binary log ve PITR · Stored procedure