MVCC-III. Fila versiones

Itaque quaestiones relatas consideravimus Nullasese recepit ordinandis notitia iaces. Et tandem venimus ad partem maxime interesting - chordae versiones.

header

Ut iam diximus, uterque ordo simul in pluribus versionibus datorum exsistere potest. Una litera ab alia quodammodo distingui debet, ad hoc, unaquaeque versio duas notas habet, quae "tempus" actionis huius versionis determinant (xmin et xmax). In quotes - quia tempus non ut talis est usus, sed peculiaris contra crescentem. Et in hoc calculo est numerus transactionis.

(Ut solet, res magis implicata est: numerus transactionis non potest augeri omne tempus propter exiguum temporis capacitatem econ.

Cum ordo creatur, xmin numerus transactioni positus est qui mandatum inserere edebat, et xmax blank relictum est.

Cum row deletum est, xmax valor emendationis hodiernae notatur numero negotiorum, qui delevit.

Cum ordo per mandatum UPDATE modificatur, duae operationes actu peraguntur: DELETE et INSERT. Praesens versio in versu ponit xmax aequalem numerum rei gestae quae UPDATE perficit. Eadem chorda nova versio postea creatur; eius valor xmin coincidit cum valore xmax prioris versionis.

In xmin et xmax agri in versu versionis capitis comprehenduntur. Praeter hos agros titulos continet alios, exempli gratia:

  • infomask series est frenorum quae proprietates huius versionis definiunt. Sunt admodum multi; Paulatim principales.
  • ctid est nexus sequentis, versio recentior eiusdem versus. Novissima enim versio chordi notissima, ctid ad ipsam versionem refertur. Numerus formam habet (x, y), ubi x est numerus paginae, y est index numeri in ordine.
  • notae illas columnas versionis datae quae nullum valorem continent (nullus). NULLUS non est unus e valoribus normalibus datae speciei, ergo attributum seorsim reponendum est.

Quam ob rem, caput satis magnum est - saltem 23 bytes pro unaquaque versione lineae, et plerumque magis ex bitmap NULL. Si mensa est "anga" (id est, paucas columnas continet), caput capere potest plus quam utilia notitia.

inserere

Propius inspiciamus quomodo operationes chordae humiles fiant, incipiendo a insertione.

Ad experimenta novam tabulam cum binis columnis et indicem in una faciamus;

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

Sit scriptor inserta uno ordine post transactionem incipiens.

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

Hic est numerus noster current transaction:

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

Intueamur contenta paginae. Acervus_page_items functionis extensionis pageinspecti te permittit ut informationes de indicibus et de versionibus actuariis habeas:

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

Nota verbum acervum in PostgreSQL refert ad tabulas. Hic alius usus vocabuli alienus - cumulus notus est data structurequae nihil cum mensa commune habet. Hic ponitur "omnia coniecta", contra indices ordinatos.

Munus indicat notitia "sicut", in forma difficili intellectu. Ut inspiciat, relinquemus tantum partem indicio ac perspicimus;

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

Hic est quod fecimus;

  • Nihilo addito ad indicem numerum ut idem ac t_ctid videatur: (numerus paginarum, index numerus).
  • Status lp_flags monstratoris extricatus est. Hic est "normalis" - id significat monstratorem re vera ad versionem chordae pertinere. Alias ​​significationes postea videbimus.
  • Omnium informationum frena tantum duo paria tantum notata sunt. Frena xmin_committenda et xmin_aborta indicant num negotium numerus xmin committitur (abortus). Duo similia frena ad transactionem numero xmax referuntur.

Quid videmus? Cum craticulam inseres, index numerus 1 in pagina tabula apparebit, primam et solam versiculi versionem ostendens.

In versione chorda, campus xminus impletur numero currenti negotio. Transactio adhuc activa est, ergo tam xmin_commissi quam xmin_abortivi freni non positi sunt.

In versu versio ctid ager eodem versu refertur. Hoc significat versionem recentiorem non esse.

Agrum xmax impletur numero phantastico 0 quia haec versio in versu deleta non est et hodie viget. Transactiones ad hunc numerum non attendent quia frenum xmax_aborted positum est.

Gradum unum sumamus ad faciliorem promptitudinem, addendo informationes minutas ad numeros transactiones. Et functionem faciamus, quia plus quam semel postulatione indigebimus;

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

In hac forma multo clarius est quid agatur in titulo de versu versionis;

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

Similia, sed signanter minus expressa, informationes ex ipsa tabula obtineri possunt, adhibitis pseudo-columnis xmin et xmax;

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

Potassium

Si res feliciter peracta est, meminisse debes eius status - nota quod committitur. Ad hoc structura, quae XACT dicta est, adhibetur (et ante versionem 10 dicta est CLOG (logum committendum) et hoc nomen diversis in locis adhuc inveniri potest).

XACT ratio catalogi tabularum non est; Hae sunt tabulae in directorio PGGATA/pg_xact. Duas partes habent pro quolibet negotio: commissum et abortivum - sicut in versu capitis versio. Haec notitia in plures tabulas divisa est solum ad commodum: ad hoc revertemur cum congelationem consideremus. Et cum his fasciculis opus est pagina pagina exercetur, sicut cum omnibus aliis.

Itaque, cum negotium in XACT committitur, frenum commissum huic negotio constituitur. Et haec omnia fiunt in committendo (quamquam de praemissa loga nondum loquimur).

Cum ad aliam rem accesserit pagina tabulae quam modo inspeximus, pluribus quaestionibus respondere debebit.

  1. Xmin rem perfecit? Si non, filum versio creata non debet esse visibilis.
    Haec perscriptio peragitur per inspectionem alterius structurae, quae in communi instantiae memoria sita est et ProcArray appellatur. Indicem omnium processuum actuum continet, et singulis numerus negotii (activi) negotii eius significatur.
  2. Si perfecit, quomodo, committendo vel collocando? Si tolleretur, tunc versio in versu vel non visa est.
    Istuc ipsum XACT est pro. Sed, quamvis ultimae paginae XACT in buffers in RAM repositae sint, tamen pretiosum est omni tempore XACT reprimere. Ergo, cum status transactionis definitus est, scriptum est ad xmin_commissos et xmin_abortos chordae versionis chordae. Si unus ex his frenis positus est, tunc status transactionis xmin notus habetur et altera transactio accessum XACT non habebit.

Cur non haec ipsa gesta inserta sunt frena? Cum inserta incidit, transactio nondum scit an successura sit. Et ex articulo peccandi iam non constat lineae quibus paginae mutatae sunt. Tales paginae multae sint, et eorum memoria inutiles. Praeterea paginae quaedam e quiddam e cache ad orbis ejici possunt; legentes eas denuo mutare, frenos signanter commendare retardarent.

Declinatio compendiorum est quod post mutationes, quaelibet res (etiamsi simplex legere - SELECT) faciendo incipere potest paginas datas mutare in quiddam cache.

Itaque mutationem notemus.

=> COMMIT;

Nihil in pagina mutatur (sed scimus transactionem status iam in XACT conscriptam esse);

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

Nunc transactiones quae paginae accessiones primum habent, statum transactionis xmin determinare debebunt et ad informationes minutas scribes;

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

removal

Cum row deletum est, numerus negotii hodiernae deletae scriptus est ad xmax campum emendationis hodiernae et frenum xmax_aborted purgatum est.

Nota valorem xmax congruentem negotii activorum ut ordo pessuli fungitur. Si alia transactio vult hunc ordinem renovare vel delere, exspectare transactionem xmax ad perficiendum cogetur. Plura de claudendo postea dicemus. Nunc enim modo animadvertimus numerum serarum infinitum esse. Spatium in RAM et ratio agendi non capiunt numerum eorum non patitur. Verum, "diu" alia incommoda habent, sed plura postea.

Lineam deleamus.

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

Rem gestam videmus numerum in xmax agro scriptum esse, sed notitiae frenorum non ponuntur;

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

Cancel

Abortus mutationes similiter committendo operatur, solum in XACT frenum abortivi pro transactioni positum est. Perii, quam celeriter committens. Etsi mandatum dicitur ROLLBACK, mutationes non sunt revolutae: omnia quae gesta sunt mutanda in notitiis paginis mutatur.

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

Cum pagina accesserit, status sedatus erit et admonitus xmax_abortus in versu versionis ponetur. Ipse numerus xmax in pagina manet, sed nemo aspiciet.

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

update

Renovatio operatur quasi primum emendationem in remigando delevit et deinde novam interposuit.

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

Quaestio unam lineam producit (versio nova):

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

Sed in pagina utramque versionem videmus:

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

Versio deleta notatur cum re praesenti numero in campo xmax. Pretium autem hoc inscriptum est in veteri, quia prior transactio abolita est. Et frenum xmax_abortum purgatum est quia status negotii hodierni nondum notus est.

Prima versio versus nunc ad secundum (t_ctid field) ut recentior.

Alter index in indice paginae et in secundo versu secundae versionis in pagina tabellae apparet.

Velut cum deletione, xmax valor in primo versu versio indicatum est in versu clausum esse.

Bene, rem perficiamus.

=> COMMIT;

indices

Hactenus de paginarum tabularum modo locuti sumus. Quid intra indices accidit?

Percontationes in indice paginarum valde secundum genus indicis certae variat. Atque etiam unum genus indices varias paginarum species habet. Exempli gratia, B-arbor pagina metadata et "regularis" paginas habet.

Sed pagina plerumque ad ordines et ipsos ordines (velut pagina tabulae) indicium habet. Praeterea in fine paginae spatium speciale datae est.

Ordines in indice possunt etiam structuras valde diversas habere pro genere indicis. Exempli gratia, pro arbore B, ordines ad paginas foliorum relatas continent valorem key index et relatio (ctid) ad ordinem tabulae respondentem. In universum index omnino alio modo erigi potest.

Praecipuum illud est quod nullae sunt versiones versuum in indicibus cuiuslibet generis. Bene, vel supponere possumus singulas lineas per unamquamque versionem repraesentari. Aliis verbis non sunt xmin et xmax agri in indice ordinis header. Ponere possumus nexus e indice ducunt ad omnes tabellas versiones ordinum - sic figurare potes ex qua versione transactionem solum videbunt spectando ad mensam. (Sicut semper, hoc non est tota veritas. In quibusdam, tabula visibilis processus optimize potest, sed hoc fusius postea videbimus).

Eodem tempore in indice paginae indicium invenimus utriusque versionis, tam hodiernam quam veterem;

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

Rectum transactions

In praxi, PostgreSQL optimizations utitur qui eam ad numeros transactionis "salvare" permittit.

Si negotium tantum notitias legit, in visibilitate ordinis versiones effectum non habet. Ergo ministerium processus primo procedit virtualiter xid ad transactionem. Numerus consistit in processu ID et numerus sequentis.

Hic numerus fiens non requirit synchronisationem inter omnes processus et ideo velocissimum est. Aliam rationem cognoscemus utendi virtualis numeri, cum de frigore loquimur.

Virtuales numeri nullo modo in notitia snapshots computantur.

Diversis temporibus, bene possunt esse virtualis transactions in systemate cum numeris iam adhibitis, quod est normale. Sed talis numerus in notas paginas describi non potest, quia tunc temporis pagina accessitur, ut omnem sensum amittat.

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

Si negotium notitias mutare incipit, reale ac singulari numero gestum datum est.

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

=> COMMIT;

Neded Opera

Nisi puncta

Definitur SQL nisi puncta quod permittitis ut partem transactionis sine interpellatione penitus rescindere. Sed hoc non convenit in supra diagrammate, cum res eundem statum habeat in omnibus mutationibus suis, et nulla notitia corporaliter revolvitur.

Ad hanc functionem efficiendam, transactio cum savepoint in plures separatas scinditur nested transactions (subtransaction), cuius status separatim tractari potest.

Res nidificant suum numerum habent (superior est numerus principalis transactionis). Status transactionum nestedarum in XACT more solito memoratur, sed status finalis in statu principalis transactionis dependet: si aboletur, tunc omnes transactiones nested etiam destruuntur.

Informationes de transactione nidificans, in documentis in PGGATA/pg_subtransversis indiciis reponitur. Tabulae accesserunt per buffers in memoriam instantia communis, eodem modo disposita ac XACT buffers.

Non confundas nidificantes res cum autonomis negotiis. Negotiationes autonomae nullo modo ab invicem dependent, sed transactiones fiunt. Nullae operationes autonomae in regulari PostgreSQL, et fortasse optimae: requiruntur valde, rarissime, et praesentia eorum in aliis DBMSs abusum provocat, unde quisque tunc laborat.

Sit mensam purgare, rem gestam inire et ordinem inserere;

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

Nunc punctum salvum faciamus et aliam lineam inseramus.

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

Nota quod txid_current() munus refert numerus transactionis principalis, non numerus transactionis nested.

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

Revolvamus ad punctum salvum et tertiam lineam inseramus.

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

In pagina continuamus videre ordinem additae a cassari nestrae transactionis.

Mutationes figimus.

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

Nunc clare perspicere potes quod unumquodque nidificans negotium suum habeat statum suum.

Nota negotiorum nidificatorum in SQL explicite adhiberi non posse, hoc est, novam transactionem incipere sine hodierna completo. Haec mechanismus implicite operatur cum usura puncta servata, tum cum exceptionibus PL/pgSQL tractandis et in multis aliis casibus exoticis magis.

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

Errores et operationum atomi

Quid accidit si error in operatione faciendo? Exempli gratia;

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

Quasi per errorem inciderunt. Transactio autem abortivi censetur, nullae operationes in eo admittuntur;

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

Etiam si mutationes committere coneris, PostgreSQL abortum referet;

=> COMMIT;
ROLLBACK

Cur negotium post defectum manere non potest? Re vera error ita oriri potuit ut accessum haberemus ad partem mutationum β€” atomicity ne quidem negotii, sed auctor violaretur. Sicut in exemplo nostro, ubi operator innovare curavit unam lineam ante errorem;

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

Dicendum est quod psql habet modum qui adhuc permittit transactionem continuare post defectum quasi actiones operantis erronei revoluti.

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

Non difficile est coniicere quod in hoc modo, psql actu implicatum, nisi punctum ante unumquodque praeceptum ponit, et in casu defectionis ad ipsum reverti initiat. Hic modus non adhibetur per defaltam, quia puncta (etiam sine revolutione) significantes caput involvit.

Continuatio.

Source: www.habr.com