MVCC-3. Dize sürümleri

Bu nedenle, ilgili konuları değerlendirdik. yalıtımve bir geri çekilme yaptı Verileri düşük düzeyde organize etmek. Ve sonunda en ilginç kısma geldik; yaylı versiyonlara.

Başlık

Daha önce de söylediğimiz gibi, her satır veritabanında aynı anda birden fazla versiyonda bulunabilir. Bir versiyon bir şekilde diğerinden ayırt edilmelidir.Bu amaçla her versiyonda, bu versiyonun etki “zamanını” belirleyen iki işaret bulunur (xmin ve xmax). Tırnak içinde - çünkü kullanılan zaman değil, özel bir artan sayaçtır. Bu sayaç ise işlem numarasıdır.

(Her zamanki gibi gerçek daha karmaşıktır: sayacın bit kapasitesinin sınırlı olması nedeniyle işlem sayısı her zaman artamaz. Ancak donma noktasına geldiğimizde bu ayrıntılara detaylı olarak bakacağız.)

Bir satır oluşturulduğunda xmin, INSERT komutunu veren işlem numarasına ayarlanır ve xmax boş bırakılır.

Bir satır silindiğinde geçerli sürümün xmax değeri, DELETE işlemini gerçekleştiren işlemin numarasıyla işaretlenir.

Bir satır UPDATE komutuyla değiştirildiğinde aslında iki işlem gerçekleştirilir: DELETE ve INSERT. Satırın geçerli sürümü, xmax'ı GÜNCELLEME'yi gerçekleştiren işlemin sayısına eşit olarak ayarlar. Daha sonra aynı dizenin yeni bir sürümü oluşturulur; xmin değeri önceki sürümün xmax değeriyle örtüşüyor.

Xmin ve xmax alanları satır sürümü başlığına dahil edilir. Başlıkta bu alanlara ek olarak başka alanlar da bulunur; örneğin:

  • bilgi maskesi, bu sürümün özelliklerini tanımlayan bir dizi bittir. Oldukça fazla var; Ana olanları yavaş yavaş ele alacağız.
  • ctid aynı satırın bir sonraki, daha yeni sürümüne bir bağlantıdır. Bir dizenin en yeni, en güncel sürümü için ctid, bu sürümün kendisini ifade eder. Sayı (x,y) biçimindedir; burada x sayfa numarasıdır, y ise dizideki dizin numarasıdır.
  • null bitmap - Belirli bir sürümün boş değer (NULL) içeren sütunlarını işaretler. NULL normal veri türü değerlerinden biri değildir, bu nedenle özniteliğin ayrı olarak saklanması gerekir.

Sonuç olarak, başlık oldukça büyüktür - satırın her sürümü için en az 23 bayt ve genellikle NULL bitmap nedeniyle daha fazladır. Tablo "dar" ise (yani birkaç sütun içeriyorsa), ek yük yararlı bilgilerden daha fazlasını kaplayabilir.

eklemek

Eklemeden başlayarak düşük seviyeli string işlemlerinin nasıl yapıldığına daha yakından bakalım.

Denemeler için iki sütunlu ve bunlardan birinde dizin bulunan yeni bir tablo oluşturalım:

=> CREATE TABLE t(
  id serial,
  s text
);
=> CREATE INDEX ON t(s);

İşlem başlattıktan sonra bir satır ekleyelim.

=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');

Güncel işlem numaramız:

=> SELECT txid_current();
 txid_current 
--------------
         3664
(1 row)

Sayfanın içeriğine bakalım. pageinspect uzantısının heap_page_items işlevi, işaretçiler ve satır sürümleri hakkında bilgi almanızı sağlar:

=> SELECT * FROM heap_page_items(get_raw_page('t',0)) gx
-[ RECORD 1 ]-------------------
lp          | 1
lp_off      | 8160
lp_flags    | 1
lp_len      | 32
t_xmin      | 3664
t_xmax      | 0
t_field3    | 0
t_ctid      | (0,1)
t_infomask2 | 2
t_infomask  | 2050
t_hoff      | 24
t_bits      | 
t_oid       | 
t_data      | x0100000009464f4f

PostgreSQL'deki heap kelimesinin tablolara atıfta bulunduğunu unutmayın. Bu da terimin başka bir garip kullanımıdır; bir yığın bilinmektedir veri yapısıtabloyla hiçbir ortak yanı yoktur. Burada kelime, sıralı indekslerin aksine "her şey bir araya getirilmiş" anlamında kullanılıyor.

İşlev, verileri anlaşılması zor bir formatta "olduğu gibi" gösterir. Bunu anlamak için bilginin yalnızca bir kısmını bırakıp şifresini çözeceğiz:

=> SELECT '(0,'||lp||')' AS ctid,
       CASE lp_flags
         WHEN 0 THEN 'unused'
         WHEN 1 THEN 'normal'
         WHEN 2 THEN 'redirect to '||lp_off
         WHEN 3 THEN 'dead'
       END AS state,
       t_xmin as xmin,
       t_xmax as xmax,
       (t_infomask & 256) > 0  AS xmin_commited,
       (t_infomask & 512) > 0  AS xmin_aborted,
       (t_infomask & 1024) > 0 AS xmax_commited,
       (t_infomask & 2048) > 0 AS xmax_aborted,
       t_ctid
FROM heap_page_items(get_raw_page('t',0)) gx
-[ RECORD 1 ]-+-------
ctid          | (0,1)
state         | normal
xmin          | 3664
xmax          | 0
xmin_commited | f
xmin_aborted  | f
xmax_commited | f
xmax_aborted  | t
t_ctid        | (0,1)

İşte yaptığımız:

  • T_ctid: (sayfa numarası, dizin numarası) ile aynı görünmesi için dizin numarasına sıfır eklendi.
  • lp_flags işaretçisinin durumu çözüldü. Burada "normal"dir - bu, işaretçinin aslında dizenin sürümünü ifade ettiği anlamına gelir. Diğer anlamlarına daha sonra bakacağız.
  • Tüm bilgi bitlerinden şu ana kadar yalnızca iki çift tanımlandı. xmin_comtained ve xmin_aborted bitleri, xmin işlem numarasının taahhüt edilip edilmediğini (iptal edildiğini) gösterir. İki benzer bit, xmax işlem numarasına karşılık gelir.

Ne görüyoruz? Bir satır eklediğinizde tablo sayfasında satırın ilk ve tek versiyonunu gösteren 1 numaralı indeks görünecektir.

String versiyonunda xmin alanı mevcut işlem numarasıyla doldurulur. İşlem hala etkin olduğundan hem xmin_comtained hem de xmin_aborted bitleri ayarlanmamıştır.

Satır sürümü ctid alanı aynı satırı ifade eder. Bu, daha yeni bir sürümün mevcut olmadığı anlamına gelir.

Satırın bu sürümü silinmediği ve güncel olduğu için xmax alanı yapay bir sayı olan 0 ile doldurulmuştur. xmax_aborted biti ayarlı olduğundan işlemlerde bu sayıya dikkat edilmeyecektir.

İşlem numaralarına bilgi bitleri ekleyerek okunabilirliği artırmaya yönelik bir adım daha atalım. Ve bir fonksiyon yaratalım, çünkü bu isteğe birden fazla kez ihtiyacımız olacak:

=> CREATE FUNCTION heap_page(relname text, pageno integer)
RETURNS TABLE(ctid tid, state text, xmin text, xmax text, t_ctid tid)
AS $$
SELECT (pageno,lp)::text::tid AS ctid,
       CASE lp_flags
         WHEN 0 THEN 'unused'
         WHEN 1 THEN 'normal'
         WHEN 2 THEN 'redirect to '||lp_off
         WHEN 3 THEN 'dead'
       END AS state,
       t_xmin || CASE
         WHEN (t_infomask & 256) > 0 THEN ' (c)'
         WHEN (t_infomask & 512) > 0 THEN ' (a)'
         ELSE ''
       END AS xmin,
       t_xmax || CASE
         WHEN (t_infomask & 1024) > 0 THEN ' (c)'
         WHEN (t_infomask & 2048) > 0 THEN ' (a)'
         ELSE ''
       END AS xmax,
       t_ctid
FROM heap_page_items(get_raw_page(relname,pageno))
ORDER BY lp;
$$ LANGUAGE SQL;

Bu formda, satır versiyonunun başlığında neler olduğu çok daha açıktır:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3664 | 0 (a) | (0,1)
(1 row)

Benzer ancak önemli ölçüde daha az ayrıntılı bilgiler, xmin ve xmax sözde sütunları kullanılarak tablonun kendisinden elde edilebilir:

=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3664 |    0 |  1 | FOO
(1 row)

tespit

Bir işlem başarıyla tamamlanırsa durumunu hatırlamanız gerekir; taahhüt edildiğini unutmayın. Bunu yapmak için XACT adı verilen bir yapı kullanılır (ve sürüm 10'dan önce buna CLOG (commit log) adı veriliyordu ve bu ad hala farklı yerlerde bulunabilir).

XACT bir sistem katalog tablosu değildir; bunlar PGDATA/pg_xact dizinindeki dosyalardır. Her işlem için ayrılmış iki bitleri vardır: taahhüt edilmiş ve iptal edilmiş - tıpkı satır sürümü başlığında olduğu gibi. Bu bilgiler yalnızca kolaylık sağlamak amacıyla birkaç dosyaya bölünmüştür; dondurmayı düşündüğümüzde bu konuya geri döneceğiz. Ve bu dosyalarla çalışma, diğerlerinde olduğu gibi sayfa sayfa gerçekleştirilir.

Dolayısıyla, XACT'ta bir işlem yürütüldüğünde, bu işlem için taahhüt edilen bit ayarlanır. Ve taahhüt sırasında gerçekleşen tek şey budur (her ne kadar henüz ön kayıt günlüğünden bahsetmiyorsak da).

Başka bir işlem az önce baktığımız tablo sayfasına eriştiğinde birkaç soruyu yanıtlamak zorunda kalacak.

  1. Xmin işlemi tamamlandı mı? Değilse, dizenin oluşturulan sürümü görünmemelidir.
    Bu kontrol, örneğin paylaşılan hafızasında yer alan ve ProcArray adı verilen başka bir yapıya bakılarak gerçekleştirilir. Tüm aktif işlemlerin bir listesini içerir ve her biri için mevcut (aktif) işlemin numarası belirtilir.
  2. Tamamlandıysa, o zaman nasıl - taahhüt ederek veya iptal ederek? İptal edilirse satır sürümü de görünmemelidir.
    XACT tam da bunun için var. Ancak XACT'ın son sayfaları RAM'deki arabelleklerde saklanmasına rağmen XACT'ı her seferinde kontrol etmek hala pahalıdır. Bu nedenle, işlem durumu belirlendikten sonra dize sürümünün xmin_comtained ve xmin_aborted bitlerine yazılır. Bu bitlerden biri ayarlanırsa xmin işleminin durumunun bilindiği kabul edilir ve bir sonraki işlemin XACT'a erişmesine gerek kalmaz.

Bu bitler neden eklemeyi yapan işlemin kendisi tarafından ayarlanmıyor? Bir ekleme gerçekleştiğinde, işlem henüz başarılı olup olmayacağını bilmiyor. Ve taahhüt anında hangi sayfaların hangi satırlarda değiştirildiği artık belli değil. Bu tür pek çok sayfa olabilir ve bunları ezberlemek kârsızdır. Ayrıca bazı sayfalar arabellek önbelleğinden diske çıkarılabilir; bitleri değiştirmek için bunları tekrar okumak, işlemi önemli ölçüde yavaşlatır.

Tasarrufun dezavantajı, değişikliklerden sonra herhangi bir işlemin (basit bir okuma - SELECT gerçekleştiren işlem bile) arabellek önbelleğindeki veri sayfalarını değiştirmeye başlayabilmesidir.

O halde değişikliği düzeltelim.

=> COMMIT;

Sayfada hiçbir şey değişmedi (ancak işlem durumunun zaten XACT'ta kayıtlı olduğunu biliyoruz):

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3664 | 0 (a) | (0,1)
(1 row)

Artık sayfaya ilk erişen işlemin xmin işlem durumunu belirlemesi ve bunu bilgi bitlerine yazması gerekecek:

=> SELECT * FROM t;
 id |  s  
----+-----
  1 | FOO
(1 row)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3664 (c) | 0 (a) | (0,1)
(1 row)

Giderme

Bir satır silindiğinde geçerli sürümün xmax alanına o anki silme işleminin numarası yazılır ve xmax_aborted biti temizlenir.

Etkin işleme karşılık gelen xmax ayar değerinin bir satır kilidi görevi gördüğünü unutmayın. Başka bir işlem bu satırı güncellemek veya silmek isterse xmax işleminin tamamlanmasını beklemek zorunda kalacaktır. Daha sonra engelleme hakkında daha fazla konuşacağız. Şimdilik sadece satır kilitlerinin sayısının sınırsız olduğunu not ediyoruz. RAM'de yer kaplamazlar ve sayılarından dolayı sistem performansı etkilenmez. Doğru, "uzun" işlemlerin başka dezavantajları da vardır, ancak bu konuda daha sonra ayrıntılı bilgi verilecektir.

Satırı silelim.

=> BEGIN;
=> DELETE FROM t;
=> SELECT txid_current();
 txid_current 
--------------
         3665
(1 row)

Xmax alanına işlem numarasının yazıldığını ancak bilgi bitlerinin ayarlanmadığını görüyoruz:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)

İptal

Değişiklikleri iptal etmek, taahhüt etmeye benzer şekilde çalışır; yalnızca XACT'ta iptal edilen bit işlem için ayarlanır. Geri alma, taahhüt etme kadar hızlıdır. Komut ROLLBACK olarak adlandırılsa da değişiklikler geri alınmaz: veri sayfalarında işlemin değiştirmeyi başardığı her şey değişmeden kalır.

=> ROLLBACK;
=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)

Sayfaya erişildiğinde durum kontrol edilecek ve xmax_aborted ipucu biti satır versiyonuna ayarlanacaktır. Xmax sayısının kendisi sayfada kalır ancak kimse ona bakmaz.

=> SELECT * FROM t;
 id |  s  
----+-----
  1 | FOO
(1 row)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   |   xmax   | t_ctid 
-------+--------+----------+----------+--------
 (0,1) | normal | 3664 (c) | 3665 (a) | (0,1)
(1 row)

Güncelleştirmek

Güncelleme, önce satırın geçerli sürümünü silmiş ve ardından yeni bir sürüm eklenmiş gibi çalışır.

=> BEGIN;
=> UPDATE t SET s = 'BAR';
=> SELECT txid_current();
 txid_current 
--------------
         3666
(1 row)

Sorgu bir satır üretir (yeni sürüm):

=> SELECT * FROM t;
 id |  s  
----+-----
  1 | BAR
(1 row)

Ancak sayfada her iki versiyonu da görüyoruz:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3664 (c) | 3666  | (0,2)
 (0,2) | normal | 3666     | 0 (a) | (0,2)
(2 rows)

Silinen sürüm, xmax alanında güncel işlem numarasıyla işaretlenir. Üstelik önceki işlem iptal edildiği için bu değer eskisinin üzerine yazılıyor. Ve mevcut işlemin durumu henüz bilinmediğinden xmax_aborted biti temizlendi.

Satırın ilk versiyonu artık ikincisini (t_ctid alanı) daha yeni olarak ifade ediyor.

Dizin sayfasında ikinci bir dizin görüntülenir ve ikinci satır, tablo sayfasındaki ikinci versiyona referans verir.

Silme işleminde olduğu gibi satırın ilk versiyonundaki xmax değeri satırın kilitli olduğunun göstergesidir.

Peki, işlemi tamamlayalım.

=> COMMIT;

Endeksler

Şu ana kadar sadece tablo sayfalarından bahsettik. Endekslerin içinde ne olur?

Dizin sayfalarındaki bilgiler, belirli dizin türüne bağlı olarak büyük ölçüde değişiklik gösterir. Ve tek bir indeks türünün bile farklı sayfa türleri vardır. Örneğin, bir B ağacının bir meta veri sayfası ve "normal" sayfaları vardır.

Ancak sayfada genellikle satırları ve satırların kendisini gösteren bir dizi işaretçi bulunur (tıpkı bir tablo sayfası gibi). Ayrıca sayfanın sonunda özel veriler için yer bulunmaktadır.

İndekslerdeki satırlar da indeks türüne bağlı olarak çok farklı yapılara sahip olabilir. Örneğin, bir B ağacı için yaprak sayfalarla ilgili satırlar, indeksleme anahtarı değerini ve karşılık gelen tablo satırına bir referansı (ctid) içerir. Genel olarak endeks tamamen farklı bir şekilde yapılandırılabilir.

En önemli nokta hiçbir tür indekste satır versiyonunun bulunmamasıdır. Ya da her satırın tam olarak bir versiyonla temsil edildiğini varsayabiliriz. Başka bir deyişle, dizin satırı başlığında xmin ve xmax alanları yoktur. Dizindeki bağlantıların satırların tüm tablo sürümlerine yol açtığını varsayabiliriz; böylece işlemin hangi sürümü göreceğini yalnızca tabloya bakarak anlayabilirsiniz. (Her zaman olduğu gibi gerçeğin tamamı bu değildir. Bazı durumlarda görünürlük haritası süreci optimize edebilir ancak buna daha sonra daha detaylı olarak bakacağız.)

Aynı zamanda, indeks sayfasında hem mevcut hem de eski olmak üzere her iki versiyona da işaretler buluyoruz:

=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
 itemoffset | ctid  
------------+-------
          1 | (0,2)
          2 | (0,1)
(2 rows)

Sanal işlemler

Uygulamada PostgreSQL, işlem numaralarını "kaydetmesine" olanak tanıyan optimizasyonlar kullanır.

Bir işlem yalnızca verileri okuyorsa satır sürümlerinin görünürlüğü üzerinde hiçbir etkisi yoktur. Bu nedenle hizmet süreci öncelikle işleme sanal bir xid verir. Numara, bir işlem kimliği ve bir sıra numarasından oluşur.

Bu numaranın verilmesi tüm süreçler arasında senkronizasyon gerektirmez ve bu nedenle çok hızlıdır. Donma hakkında konuştuğumuzda sanal sayıları kullanmanın başka bir nedeni ile tanışacağız.

Veri anlık görüntülerinde sanal sayılar hiçbir şekilde dikkate alınmaz.

Farklı zamanlarda sistemde daha önce kullanılmış numaralarla sanal işlemler yapılabilir ve bu normaldir. Ancak veri sayfalarına böyle bir sayı yazılamaz, çünkü sayfaya bir sonraki girişte anlamını yitirebilir.

=> BEGIN;
=> SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 row)

Bir işlem verileri değiştirmeye başlarsa ona gerçek, benzersiz bir işlem numarası verilir.

=> UPDATE accounts SET amount = amount - 1.00;
=> SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                     3667
(1 row)

=> COMMIT;

İç İçe İşlemler

Puanları kaydet

SQL'de tanımlandı puan tasarrufu (kaydetme noktası), bir işlemin bir kısmını tamamen kesintiye uğratmadan iptal etmenize olanak tanır. Ancak bu, yukarıdaki diyagrama uymuyor çünkü işlem tüm değişiklikler için aynı duruma sahip ve fiziksel olarak hiçbir veri geri alınmıyor.

Bu işlevselliği uygulamak için, kayıt noktası olan bir işlem birkaç ayrı parçaya bölünür. iç içe geçmiş işlemler (alt işlem), durumu ayrı olarak yönetilebilmektedir.

İç içe geçmiş işlemlerin kendi numaraları vardır (ana işlem sayısından daha yüksek). Yuvalanmış işlemlerin durumu XACT'ta olağan şekilde kaydedilir, ancak nihai durum ana işlemin durumuna bağlıdır: iptal edilirse tüm yuvalanmış işlemler de iptal edilir.

İşlem yerleştirmeyle ilgili bilgiler PGDATA/pg_subtrans dizinindeki dosyalarda saklanır. Dosyalara, örneğin paylaşılan belleğindeki, XACT arabellekleriyle aynı şekilde düzenlenen arabellekler aracılığıyla erişilir.

İç içe geçmiş işlemleri otonom işlemlerle karıştırmayın. Otonom işlemler hiçbir şekilde birbirine bağlı değildir ancak iç içe geçmiş işlemler birbirine bağlıdır. Normal PostgreSQL'de özerk işlemler yoktur ve belki de en iyisi: bunlara çok çok nadiren ihtiyaç duyulur ve diğer DBMS'lerdeki varlıkları, daha sonra herkesin maruz kalacağı kötüye kullanıma neden olur.

Tabloyu temizleyelim, bir işlem başlatalım ve satırı ekleyelim:

=> TRUNCATE TABLE t;
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
=> SELECT txid_current();
 txid_current 
--------------
         3669
(1 row)

=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
(1 row)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3669 | 0 (a) | (0,1)
(1 row)

Şimdi bir kaydetme noktası koyup bir satır daha ekleyelim.

=> SAVEPOINT sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
 txid_current 
--------------
         3669
(1 row)

Txid_current() fonksiyonunun iç içe geçmiş işlem numarasını değil ana işlem numarasını döndürdüğünü unutmayın.

=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
 3670 |    0 |  3 | XYZ
(2 rows)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3669 | 0 (a) | (0,1)
 (0,2) | normal | 3670 | 0 (a) | (0,2)
(2 rows)

Kaydetme noktasına geri dönelim ve üçüncü satırı ekleyelim.

=> ROLLBACK TO sp;
=> INSERT INTO t(s) VALUES ('BAR');
=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
 3671 |    0 |  4 | BAR
(2 rows)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3669     | 0 (a) | (0,1)
 (0,2) | normal | 3670 (a) | 0 (a) | (0,2)
 (0,3) | normal | 3671     | 0 (a) | (0,3)
(3 rows)

Sayfada iptal edilen iç içe işlemin eklediği satırı görmeye devam ediyoruz.

Değişiklikleri düzeltiyoruz.

=> COMMIT;
=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
 3671 |    0 |  4 | BAR
(2 rows)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3669 (c) | 0 (a) | (0,1)
 (0,2) | normal | 3670 (a) | 0 (a) | (0,2)
 (0,3) | normal | 3671 (c) | 0 (a) | (0,3)
(3 rows)

Artık her iç içe işlemin kendi durumuna sahip olduğunu açıkça görebilirsiniz.

İç içe geçmiş işlemlerin SQL'de açıkça kullanılamayacağını, yani mevcut işlemi tamamlamadan yeni bir işlem başlatamayacağınızı unutmayın. Bu mekanizma, kayıt noktaları kullanılırken, PL/pgSQL istisnaları ele alınırken ve diğer bazı daha egzotik durumlarda örtülü olarak etkinleştirilir.

=> BEGIN;
BEGIN
=> BEGIN;
WARNING:  there is already a transaction in progress
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
WARNING:  there is no transaction in progress
COMMIT

Hatalar ve operasyonların atomikliği

Bir işlem yapılırken hata oluşursa ne olur? Örneğin şöyle:

=> BEGIN;
=> SELECT * FROM t;
 id |  s  
----+-----
  2 | FOO
  4 | BAR
(2 rows)

=> UPDATE t SET s = repeat('X', 1/(id-4));
ERROR:  division by zero

Bir hata oluştu. Artık işlem iptal edilmiş sayılıyor ve hiçbir işleme izin verilmiyor:

=> SELECT * FROM t;
ERROR:  current transaction is aborted, commands ignored until end of transaction block

Değişiklikleri uygulamaya çalışsanız bile PostgreSQL bir iptal raporu verecektir:

=> COMMIT;
ROLLBACK

Bir işlem başarısızlıktan sonra neden devam edemiyor? Gerçek şu ki, değişikliklerin bir kısmına erişim elde etmemizi sağlayacak şekilde bir hata ortaya çıkabilir - işlemin atomikliği bile değil, operatör ihlal edilecektir. Örneğimizde olduğu gibi, operatör hatadan önce bir satırı güncellemeyi başarmıştır:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3669 (c) | 3672  | (0,4)
 (0,2) | normal | 3670 (a) | 0 (a) | (0,2)
 (0,3) | normal | 3671 (c) | 0 (a) | (0,3)
 (0,4) | normal | 3672     | 0 (a) | (0,4)
(4 rows)

Söylemek gerekir ki psql, hatalı operatörün eylemleri geri alınmış gibi, bir başarısızlıktan sonra işlemin devam etmesine izin veren bir moda sahiptir.

=> set ON_ERROR_ROLLBACK on
=> BEGIN;
=> SELECT * FROM t;
 id |  s  
----+-----
  2 | FOO
  4 | BAR
(2 rows)

=> UPDATE t SET s = repeat('X', 1/(id-4));
ERROR:  division by zero

=> SELECT * FROM t;
 id |  s  
----+-----
  2 | FOO
  4 | BAR
(2 rows)

=> COMMIT;

Bu modda psql'in aslında her komutun önüne örtülü bir kaydetme noktası koyduğunu ve başarısızlık durumunda buna geri dönüş başlattığını tahmin etmek zor değil. Bu mod varsayılan olarak kullanılmaz çünkü kayıt noktalarının ayarlanması (geri dönmeden bile) önemli miktarda ek yük gerektirir.

Devamını oku.

Kaynak: habr.com

Yorum ekle