MVCC-3. Strängversioner

Så vi har övervägt frågor relaterade till isolering, och gjorde en reträtt om organisera data på en låg nivå. Och till sist kom vi till den mest intressanta delen - strängversionerna.

Titel

Som vi redan har sagt kan varje rad samtidigt existera i flera versioner i databasen. En version måste på något sätt särskiljas från en annan. För detta ändamål har varje version två markeringar som bestämmer "tidpunkten" för denna version (xmin och xmax). Inom citattecken – för det är inte tiden som sådan som används, utan en speciell ökande räknare. Och denna räknare är transaktionsnumret.

(Som vanligt är verkligheten mer komplicerad: transaktionsnumret kan inte öka hela tiden på grund av räknarens begränsade bitkapacitet. Men vi kommer att titta på dessa detaljer i detalj när vi börjar frysa.)

När en rad skapas ställs xmin in på transaktionsnumret som gav kommandot INSERT, och xmax lämnas tomt.

När en rad raderas markeras xmax-värdet för den aktuella versionen med numret på transaktionen som utförde DELETE.

När en rad modifieras av ett UPDATE-kommando, utförs faktiskt två operationer: DELETE och INSERT. Den aktuella versionen av raden anger xmax lika med numret på transaktionen som utförde UPPDATERING. En ny version av samma sträng skapas sedan; dess xmin-värde sammanfaller med xmax-värdet för den tidigare versionen.

Fälten xmin och xmax ingår i rubriken för radversionen. Utöver dessa fält innehåller rubriken andra, till exempel:

  • infomask är en serie bitar som definierar egenskaperna för denna version. Det finns ganska många av dem; Vi kommer gradvis att överväga de viktigaste.
  • ctid är en länk till nästa, nyare version av samma rad. För den senaste, mest aktuella versionen av en linje, hänvisar ctid till denna version själv. Numret har formen (x,y), där x är sidnumret, y är indexnumret i arrayen.
  • null bitmapp - Markerar de kolumner i en given version som innehåller ett nollvärde (NULL). NULL är inte ett av de normala datatypvärdena, så attributet måste lagras separat.

Som ett resultat är rubriken ganska stor - minst 23 byte för varje version av raden, och vanligtvis mer på grund av NULL-bitmappen. Om tabellen är "smal" (det vill säga innehåller få kolumner), kan omkostnaderna ta upp mer än den användbara informationen.

infoga

Låt oss ta en närmare titt på hur strängoperationer på låg nivå utförs, med början med infogning.

För experiment, låt oss skapa en ny tabell med två kolumner och ett index på en av dem:

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

Låt oss infoga en rad efter att ha startat en transaktion.

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

Här är vårt nuvarande transaktionsnummer:

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

Låt oss titta på innehållet på sidan. Funktionen heap_page_items i tillägget pageinspect låter dig få information om pekare och radversioner:

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

Observera att ordet heap i PostgreSQL syftar på tabeller. Detta är en annan konstig användning av termen - en hög är känd datastruktur, som inte har något gemensamt med tabellen. Här används ordet i betydelsen "allt slängs ihop", i motsats till ordnade index.

Funktionen visar data "i befintligt skick", i ett format som är svårt att förstå. För att ta reda på det lämnar vi bara en del av informationen och dechiffrerar den:

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

Så här gjorde vi:

  • Lade till en nolla till indexnumret för att få det att se ut som t_ctid: (sidnummer, indexnummer).
  • Dechiffrerade tillståndet för lp_flags-pekaren. Här är det "normalt" - det betyder att pekaren faktiskt refererar till versionen av strängen. Vi kommer att titta på andra betydelser senare.
  • Av alla informationsbitar har bara två par identifierats hittills. Bitarna xmin_committed och xmin_aborted indikerar om transaktionsnumret xmin har begåtts (avbruten). Två liknande bitar hänvisar till transaktionsnummer xmax.

Vad ser vi? När du infogar en rad visas ett indexnummer 1 på tabellsidan som pekar på den första och enda versionen av raden.

I strängversionen fylls xmin-fältet med det aktuella transaktionsnumret. Transaktionen är fortfarande aktiv, så både bitarna xmin_committed och xmin_aborted är inte inställda.

Radversionens ctid-fält hänvisar till samma rad. Det betyder att en nyare version inte existerar.

Fältet xmax är fyllt med ett dummynummer 0 eftersom denna version av raden inte har raderats och är aktuell. Transaktioner kommer inte att uppmärksamma detta nummer eftersom biten xmax_aborted är inställd.

Låt oss ta ytterligare ett steg mot att förbättra läsbarheten genom att lägga till informationsbitar i transaktionsnummer. Och låt oss skapa en funktion, eftersom vi kommer att behöva begäran mer än en gång:

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

I det här formuläret är det mycket tydligare vad som händer i rubriken på radversionen:

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

Liknande, men betydligt mindre detaljerad, information kan erhållas från själva tabellen med hjälp av pseudo-kolumner xmin och xmax:

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

Fixering

Om en transaktion slutförs framgångsrikt måste du komma ihåg dess status - observera att den är genomförd. För att göra detta används en struktur som heter XACT (och innan version 10 hette den CLOG (commit log) och detta namn finns fortfarande på olika ställen).

XACT är inte en systemkatalogtabell; det här är filerna i katalogen PGDATA/pg_xact. De har två bitar för varje transaktion: begångna och avbrutna - precis som i rubriken för radversionen. Denna information är uppdelad i flera filer enbart för bekvämlighets skull; vi återkommer till denna fråga när vi överväger att frysa. Och arbetet med dessa filer utförs sida för sida, som med alla andra.

Så, när en transaktion committeras i XACT, ställs den committerade biten in för denna transaktion. Och detta är allt som händer under committing (även om vi inte pratar om förinspelningsloggen ännu).

När en annan transaktion kommer åt tabellsidan vi just tittade på, måste den svara på flera frågor.

  1. Har xmin-transaktionen slutförts? Om inte, bör den skapade versionen av strängen inte vara synlig.
    Denna kontroll utförs genom att titta på en annan struktur, som finns i instansens delade minne och kallas ProcArray. Den innehåller en lista över alla aktiva processer, och för var och en anges numret på dess aktuella (aktiva) transaktion.
  2. Om det är klart, hur - genom att binda eller avbryta? Om den avbryts bör radversionen inte heller vara synlig.
    Det är precis vad XACT är till för. Men även om de sista sidorna i XACT lagras i buffertar i RAM, är det fortfarande dyrt att kontrollera XACT varje gång. Därför, när transaktionsstatusen har bestämts, skrivs den till bitarna xmin_committed och xmin_aborted i strängversionen. Om en av dessa bitar är inställd, anses tillståndet för transaktionen xmin vara känt och nästa transaktion behöver inte komma åt XACT.

Varför sätts inte dessa bitar av själva transaktionen som gör insättningen? När en infogning sker vet transaktionen ännu inte om den kommer att lyckas. Och vid bindningsögonblicket är det inte längre klart vilka rader på vilka sidor som ändrades. Det kan finnas många sådana sidor, och det är olönsamt att memorera dem. Dessutom kan vissa sidor kastas från buffertcachen till disken; att läsa dem igen för att ändra bitarna skulle sakta ner commit avsevärt.

Nackdelen med besparingarna är att efter ändringar kan alla transaktioner (även en som utför en enkel läsning - SELECT) börja ändra datasidor i buffertcachen.

Så låt oss fixa förändringen.

=> COMMIT;

Inget har ändrats på sidan (men vi vet att transaktionsstatusen redan är registrerad i XACT):

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

Nu måste transaktionen som först kommer åt sidan bestämma xmin-transaktionsstatusen och skriva den till informationsbitarna:

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

Borttagning

När en rad raderas skrivs numret på den aktuella borttagningstransaktionen till xmax-fältet för den aktuella versionen och xmax_aborted-biten rensas.

Observera att det inställda värdet på xmax som motsvarar den aktiva transaktionen fungerar som ett radlås. Om en annan transaktion vill uppdatera eller ta bort den här raden, kommer den att tvingas vänta på att transaktionen xmax ska slutföras. Vi kommer att prata mer om blockering senare. För närvarande noterar vi bara att antalet radlås är obegränsat. De tar inte upp plats i RAM-minnet och systemets prestanda lider inte av deras antal. Visserligen har "långa" transaktioner andra nackdelar, men mer om det senare.

Låt oss ta bort raden.

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

Vi ser att transaktionsnumret är skrivet i xmax-fältet, men informationsbitarna är inte inställda:

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

Avbokning

Att avbryta ändringar fungerar på samma sätt som att begå, bara i XACT är den avbrutna biten inställd för transaktionen. Att ångra är lika snabbt som att begå. Även om kommandot heter ROLLBACK, återställs inte ändringarna: allt som transaktionen lyckades ändra på datasidorna förblir oförändrat.

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

När sidan öppnas kommer statusen att kontrolleras och xmax_aborted hint-biten kommer att ställas in på radversionen. Själva xmax-talet finns kvar på sidan, men ingen kommer att titta på det.

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

Uppdatera

Uppdateringen fungerar som om den först tog bort den aktuella versionen av raden och sedan infogade en ny.

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

Frågan producerar en rad (ny version):

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

Men på sidan ser vi båda versionerna:

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

Den raderade versionen markeras med aktuellt transaktionsnummer i xmax-fältet. Dessutom skrivs detta värde över det gamla, eftersom den tidigare transaktionen avbröts. Och biten xmax_aborted rensas eftersom statusen för den aktuella transaktionen ännu inte är känd.

Den första versionen av raden hänvisar nu till den andra (t_ctid-fältet) som den nyare.

Ett andra index visas på indexsidan och en andra rad hänvisar till den andra versionen på tabellsidan.

Precis som vid radering är xmax-värdet i den första versionen av raden en indikation på att raden är låst.

Nåväl, låt oss slutföra transaktionen.

=> COMMIT;

Index

Hittills har vi bara pratat om tabellsidor. Vad händer i indexen?

Informationen på indexsidor varierar mycket beroende på den specifika typen av index. Och även en typ av index har olika typer av sidor. Till exempel har ett B-träd en metadatasida och "vanliga" sidor.

Men sidan har vanligtvis en rad pekare till raderna och själva raderna (precis som en tabellsida). Dessutom finns det i slutet av sidan plats för specialdata.

Rader i index kan också ha väldigt olika struktur beroende på typ av index. Till exempel, för ett B-träd, innehåller raderna relaterade till bladsidor indexeringsnyckelvärdet och en referens (ctid) till motsvarande tabellrad. I allmänhet kan indexet vara uppbyggt på ett helt annat sätt.

Den viktigaste punkten är att det inte finns några radversioner i index av någon typ. Tja, eller så kan vi anta att varje linje representeras av exakt en version. Det finns med andra ord inga xmin- och xmax-fält i indexradshuvudet. Vi kan anta att länkar från indexet leder till alla tabellversioner av raderna - så att du kan ta reda på vilken version transaktionen kommer att se endast genom att titta på tabellen. (Som alltid är detta inte hela sanningen. I vissa fall kan synlighetskartan optimera processen, men vi kommer att titta på detta mer i detalj senare.)

Samtidigt hittar vi på indexsidan pekare till båda versionerna, både den nuvarande och den gamla:

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

Virtuella transaktioner

I praktiken använder PostgreSQL optimeringar som gör att det kan "spara" transaktionsnummer.

Om en transaktion bara läser data har det ingen effekt på radversionernas synlighet. Därför utfärdar serviceprocessen först en virtuell xid till transaktionen. Numret består av ett process-ID och ett sekvensnummer.

Att utfärda detta nummer kräver inte synkronisering mellan alla processer och är därför mycket snabbt. Vi kommer att bekanta oss med en annan anledning till att använda virtuella nummer när vi pratar om frysning.

Virtuella siffror beaktas inte på något sätt i ögonblicksbilder av data.

Vid olika tidpunkter kan det mycket väl finnas virtuella transaktioner i systemet med nummer som redan har använts, och det är normalt. Men ett sådant nummer kan inte skrivas in i datasidor, eftersom nästa gång sidan öppnas kan den förlora all betydelse.

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

Om en transaktion börjar ändra data får den ett riktigt, unikt transaktionsnummer.

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

=> COMMIT;

Kapslade transaktioner

Spara poäng

Definierat i SQL spara poäng (savepoint), som låter dig avbryta en del av en transaktion utan att avbryta den helt. Men detta passar inte in i diagrammet ovan, eftersom transaktionen har samma status för alla sina ändringar, och fysiskt rullas ingen data tillbaka.

För att implementera denna funktionalitet delas en transaktion med en sparpunkt upp i flera separata kapslade transaktioner (deltransaktion), vars status kan hanteras separat.

Kapslade transaktioner har sitt eget nummer (högre än numret på huvudtransaktionen). Statusen för kapslade transaktioner registreras på vanligt sätt i XACT, men den slutliga statusen beror på statusen för huvudtransaktionen: om den avbryts avbryts också alla kapslade transaktioner.

Information om transaktionskapsling lagras i filer i katalogen PGDATA/pg_subtrans. Filer nås via buffertar i instansens delade minne, organiserade på samma sätt som XACT-buffertar.

Blanda inte ihop kapslade transaktioner med autonoma transaktioner. Autonoma transaktioner är inte beroende av varandra på något sätt, men kapslade transaktioner gör det. Det finns inga autonoma transaktioner i vanlig PostgreSQL, och kanske till det bästa: de behövs väldigt, väldigt sällan, och deras närvaro i andra DBMS provocerar fram missbruk, vilket alla sedan lider av.

Låt oss rensa tabellen, starta en transaktion och infoga raden:

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

Låt oss nu sätta en räddningspunkt och infoga en annan rad.

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

Observera att funktionen txid_current() returnerar huvudtransaktionsnumret, inte det kapslade transaktionsnumret.

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

Låt oss rulla tillbaka till sparpunkten och infoga den tredje raden.

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

På sidan fortsätter vi att se raden som lagts till av den avbrutna kapslade transaktionen.

Vi fixar ändringarna.

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

Nu kan du tydligt se att varje kapslad transaktion har sin egen status.

Observera att kapslade transaktioner inte kan användas explicit i SQL, det vill säga du kan inte starta en ny transaktion utan att slutföra den aktuella. Denna mekanism aktiveras implicit vid användning av savepoints, såväl som vid hantering av PL/pgSQL-undantag och i ett antal andra, mer exotiska fall.

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

Fel och atomicitet av operationer

Vad händer om ett fel uppstår när en operation utförs? Till exempel så här:

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

Ett fel har uppstått. Nu anses transaktionen vara avbruten och inga operationer är tillåtna i den:

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

Och även om du försöker genomföra ändringarna kommer PostgreSQL att rapportera en avbrytning:

=> COMMIT;
ROLLBACK

Varför kan inte en transaktion fortsätta efter ett misslyckande? Faktum är att ett fel skulle kunna uppstå på ett sådant sätt att vi skulle få tillgång till en del av förändringarna - atomiciteten för inte ens transaktionen, utan operatören skulle kränkas. Som i vårt exempel, där operatören lyckades uppdatera en rad före felet:

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

Det måste sägas att psql har ett läge som fortfarande tillåter transaktionen att fortsätta efter ett misslyckande som om åtgärderna från den felaktiga operatören rullades tillbaka.

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

Det är inte svårt att gissa att i det här läget sätter psql faktiskt en implicit räddningspunkt före varje kommando, och i händelse av fel initierar en återställning till den. Det här läget används inte som standard, eftersom inställning av räddningspunkter (även utan att gå tillbaka till dem) innebär betydande omkostnader.

Fortsättning.

Källa: will.com

Lägg en kommentar