MVCC-3. Верзии на жици

Значи, разгледавме прашања поврзани со изолација, и направи повлекување за организирање податоци на ниско ниво. И конечно стигнавме до најинтересниот дел - жичените верзии.

Титула

Како што веќе рековме, секој ред може истовремено да постои во неколку верзии во базата на податоци. Една верзија мора некако да се разликува од друга.За таа цел, секоја верзија има две ознаки кои го одредуваат „времето“ на дејствување на оваа верзија (xmin и xmax). Во наводници - затоа што не се користи време како такво, туку посебен бројач за зголемување. И овој бројач е бројот на трансакцијата.

(Како и обично, реалноста е покомплицирана: бројот на трансакцијата не може постојано да се зголемува поради ограничениот бит капацитет на бројачот. Но, овие детали ќе ги разгледаме детално кога ќе дојдеме до замрзнување.)

Кога ќе се креира ред, xmin се поставува на бројот на трансакцијата што ја издал командата INSERT, а xmax се остава празно.

Кога некој ред е избришан, xmax вредноста на тековната верзија се означува со бројот на трансакцијата што го извршила DELETE.

Кога редот е изменет со команда UPDATE, всушност се извршуваат две операции: DELETE и INSERT. Тековната верзија на редот поставува xmax еднаков на бројот на трансакцијата што го изврши АЖУРИРАЊЕТО. Потоа се креира нова верзија на истата низа; неговата вредност xmin се совпаѓа со вредноста xmax од претходната верзија.

Полињата xmin и xmax се вклучени во насловот на верзијата на редот. Покрај овие полиња, заглавието содржи и други, на пример:

  • infomask е серија од битови кои ги дефинираат својствата на оваа верзија. Има доста од нив; Постепено ќе ги разгледаме главните.
  • ctid е врска до следната, понова верзија на истата линија. За најновата, најактуелна верзија на стрингот, ctid се однесува на самата оваа верзија. Бројот има форма (x,y), каде што x е бројот на страницата, y е индексниот број во низата.
  • null bitmap - Ги означува оние колони од дадената верзија што содржат нула вредност (NULL). NULL не е една од нормалните вредности на типот на податоци, затоа атрибутот мора да се складира одделно.

Како резултат на тоа, заглавието е прилично големо - најмалку 23 бајти за секоја верзија на линијата, и обично повеќе поради NULL битмапата. Ако табелата е „тесна“ (односно, содржи неколку колони), горните трошоци може да заземат повеќе од корисните информации.

вметнете

Да разгледаме подетално како се изведуваат операциите со низа на ниско ниво, почнувајќи со вметнување.

За експерименти, ајде да создадеме нова табела со две колони и индекс на една од нив:

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

Ајде да вметнеме еден ред откако ќе започнеме трансакција.

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

Еве го нашиот тековен број на трансакција:

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

Ајде да ја погледнеме содржината на страницата. Функцијата heap_page_items на наставката pageinspect ви овозможува да добивате информации за покажувачите и верзиите на редови:

=> 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 се однесува на табели. Ова е уште една чудна употреба на терминот - се знае куп структура на податоци, што нема ништо заедничко со табелата. Овде зборот се користи во смисла на „сè е фрлено заедно“, за разлика од нарачаните индекси.

Функцијата ги прикажува податоците „како што се“, во формат што е тешко разбирлив. За да го разбереме, ќе оставиме само дел од информациите и ќе ги дешифрираме:

=> 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)

Еве што направивме:

  • Додаде нула на бројот на индексот за да изгледа исто како t_ctid: (број на страница, број на индекс).
  • Ја дешифрираше состојбата на покажувачот lp_flags. Овде е „нормално“ - тоа значи дека покажувачот всушност се однесува на верзијата на низата. Подоцна ќе ги разгледаме другите значења.
  • Од сите информативни битови, досега се идентификувани само два пара. Битовите xmin_committed и xmin_aborted покажуваат дали бројот на трансакцијата xmin е посветен (отфрлен). Два слични бита се однесуваат на бројот на трансакција xmax.

Што гледаме? Кога вметнувате ред, на страницата со табелата ќе се појави индекс број 1, што укажува на првата и единствената верзија на редот.

Во стринг верзијата, полето xmin се пополнува со тековниот број на трансакција. Трансакцијата е сè уште активна, така што и битовите xmin_committed и xmin_aborted не се поставени.

Полето ctid верзија на ред се однесува на истиот ред. Тоа значи дека понова верзија не постои.

Полето xmax е исполнето со лажен број 0 бидејќи оваа верзија на редот не е избришана и е актуелна. Трансакциите нема да обрнат внимание на овој број бидејќи е поставен битот xmax_aborted.

Ајде да направиме уште еден чекор кон подобрување на читливоста со додавање на информативни битови на броевите на трансакциите. И ајде да создадеме функција, бидејќи барањето ќе ни треба повеќе од еднаш:

=> 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;

Во оваа форма, многу е појасно што се случува во заглавието на верзијата на редот:

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

Слични, но значително помалку детални, информации може да се добијат од самата табела, користејќи псевдо-колони xmin и xmax:

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

Фиксација

Ако трансакцијата е успешно завршена, треба да го запомните нејзиниот статус - забележете дека е посветена. За да го направите ова, се користи структура наречена XACT (а пред верзијата 10 беше наречена CLOG (дневник за обврзување) и ова име сè уште може да се најде на различни места).

XACT не е системски каталог табела; ова се датотеките во директориумот PGDATA/pg_xact. Тие имаат два бита доделени за секоја трансакција: извршена и прекината - исто како во заглавието на верзијата на редот. Оваа информација е поделена на неколку датотеки исклучиво за погодност; ние ќе се вратиме на ова прашање кога ќе размислиме за замрзнување. И работата со овие датотеки се врши страница по страница, како и со сите други.

Значи, кога трансакцијата е извршена во XACT, посветениот бит е поставен за оваа трансакција. И ова е сè што се случува за време на извршувањето (иако сè уште не зборуваме за дневникот за претходно снимање).

Кога друга трансакција ќе пристапи до страницата со табелата што штотуку ја погледнавме, ќе треба да одговори на неколку прашања.

  1. Дали xmin трансакцијата е завршена? Ако не, тогаш креираната верзија на низата не треба да биде видлива.
    Оваа проверка се врши со гледање на друга структура, која се наоѓа во споделената меморија на инстанцата и се нарекува ProcArray. Содржи список на сите активни процеси и за секој е означен бројот на неговата тековна (активна) трансакција.
  2. Ако е завршено, тогаш како - со извршување или откажување? Ако се откаже, тогаш не треба да се гледа ниту верзијата на редот.
    Токму за ова е XACT. Но, иако последните страници на XACT се зачувани во бафери во RAM меморијата, сепак е скапо да се проверува XACT секој пат. Затоа, откако ќе се утврди статусот на трансакцијата, тој се запишува на битовите xmin_committed и xmin_aborted од верзијата на стрингот. Ако еден од овие битови е поставен, тогаш состојбата на трансакцијата xmin се смета за позната и следната трансакција нема да мора да пристапи до XACT.

Зошто овие битови не се поставени од самата трансакција при вметнувањето? Кога ќе се појави вметнување, трансакцијата сè уште не знае дали ќе успее. И во моментот на извршување, веќе не е јасно во кои редови во кои страници се сменети. Можеби има многу такви страници, а нивното меморирање е непрофитабилно. Дополнително, некои страници може да се исфрлат од тампон-кешот на дискот; ако ги прочитате повторно за да ги промените битовите значително ќе го забави извршувањето.

Негативната страна на заштедите е што по промените, секоја трансакција (дури и онаа што врши едноставно читање - SELECT) може да почне да ги менува страниците со податоци во кешот на баферот.

Значи, да ја поправиме промената.

=> COMMIT;

Ништо не е променето на страницата (но знаеме дека статусот на трансакцијата е веќе снимен во XACT):

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

Сега трансакцијата што прво пристапува до страницата ќе треба да го одреди статусот на трансакцијата xmin и да ја запише на битовите за информации:

=> 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)

Бришење

Кога се брише ред, бројот на тековната трансакција за бришење се запишува во полето xmax на тековната верзија, а битот xmax_aborted се брише.

Забележете дека поставената вредност на xmax што одговара на активната трансакција делува како заклучување на редот. Ако друга трансакција сака да го ажурира или избрише овој ред, ќе биде принуден да чека да заврши трансакцијата xmax. Подоцна ќе зборуваме повеќе за блокирање. Засега, само забележуваме дека бројот на заклучувања на редови е неограничен. Тие не заземаат простор во RAM меморијата и перформансите на системот не страдаат од нивниот број. Навистина, „долгите“ трансакции имаат и други недостатоци, но повеќе за тоа подоцна.

Ајде да ја избришеме линијата.

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

Гледаме дека бројот на трансакцијата е запишан во полето xmax, но битовите за информации не се поставени:

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

Откажување

Абортираните промени функционираат слично како и извршувањето, само во XACT прекинатиот бит е поставен за трансакцијата. Поништувањето е исто толку брзо како и извршувањето. Иако командата се нарекува ROLLBACK, промените не се враќаат назад: сè што трансакцијата успеала да промени на страниците со податоци останува непроменето.

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

Кога ќе се пристапи на страницата, статусот ќе се провери и битот за совет xmax_aborted ќе биде поставен на верзијата на редот. Самиот xmax број останува на страницата, но никој нема да го погледне.

=> 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)

Ажурирање

Ажурирањето работи како прво да ја избриша тековната верзија на редот, а потоа да вметне нова.

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

Барањето произведува една линија (нова верзија):

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

Но, на страницата ги гледаме двете верзии:

=> 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)

Избришаната верзија е означена со тековниот број на трансакција во полето xmax. Покрај тоа, оваа вредност е запишана над старата, бидејќи претходната трансакција беше откажана. И битот xmax_aborted е исчистен бидејќи статусот на тековната трансакција сè уште не е познат.

Првата верзија на линијата сега се однесува на второто (t_ctid поле) како понова.

Вториот индекс се појавува на страницата со индекс, а вториот ред упатува на втората верзија на страницата со табелата.

Исто како и со бришењето, вредноста на xmax во првата верзија на редот е показател дека редот е заклучен.

Па, ајде да ја завршиме трансакцијата.

=> COMMIT;

Индекси

Досега зборувавме само за страници од табелата. Што се случува внатре во индексите?

Информациите во индексните страници многу варираат во зависност од специфичниот тип на индекс. Па дури и еден тип на индекс има различни типови на страници. На пример, Б-дрвото има страница со метаподатоци и „обични“ страници.

Сепак, страницата обично има низа од покажувачи кон редовите и самите редови (исто како страницата со табела). Покрај тоа, на крајот од страницата има простор за посебни податоци.

Редовите во индексите исто така може да имаат многу различни структури во зависност од видот на индексот. На пример, за B-дрво, редовите поврзани со листовите ја содржат вредноста на клучот за индексирање и референца (ctid) до соодветниот ред од табелата. Генерално, индексот може да се структурира на сосема поинаков начин.

Најважната точка е дека нема верзии на редови во индекси од кој било тип. Па, или можеме да претпоставиме дека секоја линија е претставена со точно една верзија. Со други зборови, нема xmin и xmax полиња во заглавието на редот на индексот. Можеме да претпоставиме дека врските од индексот водат до сите верзии на табелите на редовите - така што можете да дознаете која верзија трансакцијата ќе ја види само со гледање на табелата. (Како и секогаш, ова не е целата вистина. Во некои случаи, мапата за видливост може да го оптимизира процесот, но ова ќе го разгледаме подетално подоцна.)

Во исто време, на страницата со индекс наоѓаме покажувачи на двете верзии, и тековната и старата:

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

Виртуелни трансакции

Во пракса, PostgreSQL користи оптимизации кои му дозволуваат да ги „зачува“ броевите на трансакциите.

Ако трансакцијата само чита податоци, тоа нема ефект врз видливоста на верзиите на редови. Затоа, процесот на услуга прво издава виртуелен xid на трансакцијата. Бројот се состои од ID на процес и секвенционен број.

Издавањето на овој број не бара синхронизација помеѓу сите процеси и затоа е многу брзо. Со уште една причина за користење виртуелни броеви ќе се запознаеме кога зборуваме за замрзнување.

Виртуелните броеви на никаков начин не се земаат предвид во снимките од податоците.

Во различни моменти во времето, може да има виртуелни трансакции во системот со веќе користени броеви, и тоа е нормално. Но, таков број не може да се запише на страниците со податоци, бидејќи следниот пат кога ќе се пристапи на страницата може да го изгуби сето значење.

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

Ако трансакцијата почне да ги менува податоците, ѝ се дава вистински, единствен број на трансакција.

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

=> COMMIT;

Вгнездени трансакции

Зачувајте поени

Дефинирано во SQL зачувај поени (savepoint), кои ви дозволуваат да откажете дел од трансакцијата без целосно да ја прекинете. Но, ова не се вклопува во горната шема, бидејќи трансакцијата има ист статус за сите нејзини промени, а физички не се враќаат никакви податоци.

За да се имплементира оваа функционалност, трансакцијата со зачувана точка е поделена на неколку одделни вгнездени трансакции (подтрансакција), чиј статус може да се управува одделно.

Вгнездените трансакции имаат свој број (повисок од бројот на главната трансакција). Статусот на вгнездените трансакции се евидентира на вообичаен начин во XACT, но конечниот статус зависи од статусот на главната трансакција: ако е откажана, тогаш се откажуваат и сите вгнездени трансакции.

Информациите за вгнездувањето на трансакциите се зачувуваат во датотеки во директориумот PGDATA/pg_subtrans. Додатоците се пристапуваат преку бафери во споделената меморија на примерокот, организирани на ист начин како и XACT баферите.

Не мешајте вгнездени трансакции со автономни трансакции. Автономните трансакции не зависат една од друга на кој било начин, туку вгнездените трансакции зависат. Нема автономни трансакции во редовните PostgreSQL и, можеби, најдобро: тие се потребни многу, многу ретко, а нивното присуство во други DBMS предизвикува злоупотреба, од која потоа сите страдаат.

Ајде да ја исчистиме табелата, да започнеме трансакција и да го вметнеме редот:

=> 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)

Сега да ставиме точка за зачувување и да вметнеме друга линија.

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

Забележете дека функцијата txid_current() го враќа главниот број на трансакција, а не вгнездениот број на трансакција.

=> 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)

Ајде да се вратиме на точката за зачувување и да ја вметнеме третата линија.

=> 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)

На страницата продолжуваме да го гледаме редот додаден од откажаната вгнездена трансакција.

Ги поправаме промените.

=> 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)

Сега можете јасно да видите дека секоја вгнездена трансакција има свој статус.

Забележете дека вгнездените трансакции не можат да се користат експлицитно во SQL, односно не можете да започнете нова трансакција без да ја завршите тековната. Овој механизам се активира имплицитно при користење на зачувани точки, како и при ракување со PL/pgSQL исклучоци и во голем број други, поегзотични случаи.

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

Грешки и атомичноста на операциите

Што се случува ако се појави грешка при извршување на операција? На пример, вака:

=> 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;
ERROR:  current transaction is aborted, commands ignored until end of transaction block

И дури и ако се обидете да ги извршите промените, PostgreSQL ќе пријави прекин:

=> COMMIT;
ROLLBACK

Зошто трансакцијата не може да продолжи по неуспех? Факт е дека може да настане грешка на таков начин што ќе добиеме пристап до дел од промените - атомичноста дури ни на трансакцијата, туку на операторот би била нарушена. Како во нашиот пример, каде што операторот успеа да ажурира една линија пред грешката:

=> 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)

Мора да се каже дека psql има режим кој сè уште дозволува трансакцијата да продолжи по неуспех како да се вратени дејствата на погрешниот оператор.

=> 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;

Не е тешко да се погоди дека во овој режим, psql всушност става имплицитна точка за зачувување пред секоја команда и во случај на неуспех иницира враќање на истата. Овој режим не се користи стандардно, бидејќи поставувањето точки за зачувување (дури и без враќање назад кон нив) вклучува значителни трошоци.

Продолжување.

Извор: www.habr.com

Додадете коментар