sql >> Database teknologi >  >> RDS >> PostgreSQL

Dvale annotering for PostgreSQL seriel type

Fare: Dit spørgsmål antyder, at du muligvis laver en designfejl - du forsøger at bruge en databasesekvens til en "forretningsværdi", som præsenteres for brugerne, i dette tilfælde fakturanumre.

Brug ikke en sekvens, hvis du har brug for mere end at teste værdien for lighed. Den har ingen orden. Den har ingen "afstand" fra en anden værdi. Det er bare lige, eller ikke lige.

Tilbage: Sekvenser er generelt ikke passende til sådanne anvendelser, fordi ændringer af sekvenser ikke rulles tilbage med transaktionen ROLLBACK . Se sidefødderne om functions-sequence og CREATE SEQUENCE .

Tilbageføringer forventes og normalt. De opstår på grund af:

  • deadlocks forårsaget af modstridende opdateringsrækkefølge eller andre låse mellem to transaktioner;
  • optimistiske tilbagerulninger af låse i Hibernate;
  • forbigående klientfejl;
  • servervedligeholdelse af DBA;
  • serialiseringskonflikter i SERIALIZABLE eller øjebliksbillede-isoleringstransaktioner

... og mere.

Din ansøgning vil have "huller" i fakturanummereringen, hvor disse tilbagerulninger sker. Derudover er der ingen bestillingsgaranti, så det er fuldt ud muligt, at en transaktion med et senere sekvensnummer vil begå tidligere (nogle gange meget tidligere) end en med et senere nummer.

Chunking:

Det er også normalt for nogle applikationer, inklusive Hibernate, at hente mere end én værdi fra en sekvens ad gangen og dele dem ud til transaktioner internt. Det er tilladt, fordi du ikke skal forvente, at sekvensgenererede værdier har nogen meningsfuld rækkefølge eller kan sammenlignes på nogen måde, bortset fra lighed. Til fakturanummerering ønsker du også at bestille, så du bliver slet ikke glad, hvis Hibernate griber værdierne 5900-5999 og begynder at dele dem ud fra 5999 og tæller ned eller skiftevis op-og-ned, så dine fakturanumre går:n, n+1, n+49, n+2, n+48, ... n+50, n+99, n+51, n+98, [n+52 tabt til tilbagerulning], n+97, ... . Ja, høj-derefter-lav-allokatoren findes i Hibernate.

Det hjælper ikke, medmindre du definerer individuel @SequenceGenerator I dine kortlægninger deler Hibernate gerne en enkelt sekvens for hver også genereret ID. Grimme.

Korrekt brug:

En sekvens er kun passende, hvis du kun kræver, at nummereringen er unik. Hvis du også har brug for, at det er monotont og ordinært, bør du overveje at bruge en almindelig tabel med et tællerfelt via UPDATE ... RETURNING eller SELECT ... FOR UPDATE ("pessimistisk låsning" i Hibernate) eller via Hibernate optimistisk låsning. På den måde kan du garantere mellemrumsfrie trin uden huller eller ude af rækkefølge.

Hvad skal du gøre i stedet:

Lav et bord kun til en disk. Hav en enkelt række i den, og opdater den, mens du læser den. Det låser den og forhindrer andre transaktioner i at få et id, indtil din forpligter sig.

Fordi det tvinger alle dine transaktioner til at fungere serielt, så prøv at holde transaktioner, der genererer faktura-id'er korte og undgå at arbejde mere i dem, end du behøver.

CREATE TABLE invoice_number (
    last_invoice_number integer primary key
);

-- PostgreSQL specific hack you can use to make
-- really sure only one row ever exists
CREATE UNIQUE INDEX there_can_be_only_one 
ON invoice_number( (1) );

-- Start the sequence so the first returned value is 1
INSERT INTO invoice_number(last_invoice_number) VALUES (0);

-- To get a number; PostgreSQL specific but cleaner.
-- Use as a native query from Hibernate.
UPDATE invoice_number
SET last_invoice_number = last_invoice_number + 1
RETURNING last_invoice_number;

Alternativt kan du:

  • Definer en enhed for invoice_number, tilføj en @Version kolonne, og lad optimistisk låsning tage sig af konflikter;
  • Definer en enhed for invoice_number og brug eksplicit pessimistisk låsning i Hibernate for at vælge ... for opdatering og derefter en opdatering.

Alle disse muligheder vil serialisere dine transaktioner - enten ved at rulle konflikter tilbage ved hjælp af @Version, eller ved at blokere dem (låse), indtil låseholderen forpligter sig. Uanset hvad, vil sekvenser uden gap virkelig sænk det område af din ansøgning ned, så brug kun sekvenser uden mellemrum, når det er nødvendigt.

@GenerationType.TABLE :Det er fristende at bruge @GenerationType.TABLE med en @TableGenerator(initialValue=1, ...) . Selvom GenerationType.TABLE lader dig angive en tildelingsstørrelse via @TableGenerator, giver den desværre ingen garantier for bestillings- eller rollback-adfærd. Se JPA 2.0-specifikationen, afsnit 11.1.46 og 11.1.17. Især "Denne specifikation definerer ikke den nøjagtige adfærd af disse strategier. og fodnote 102 "Bærbare applikationer bør ikke bruge GeneratedValue-annotationen på andre vedvarende felter eller egenskaber [end @Id primære nøgler]" . Så det er usikkert at bruge @GenerationType.TABLE for nummerering, som du har brug for skal være hulfri eller nummerering, der ikke er på en primær nøgleejendom, medmindre din JPA-udbyder giver flere garantier end standarden.

Hvis du sidder fast med en sekvens :

Plakaten bemærker, at de har eksisterende apps, der bruger DB, der allerede bruger en sekvens, så de sidder fast med den.

JPA-standarden garanterer ikke, at du kan bruge genererede kolonner undtagen på @Id, du kan (a) ignorere det og gå videre, så længe din udbyder tillader det, eller (b) indsætte med en standardværdi og re -læse fra databasen. Sidstnævnte er mere sikkert:

    @Column(name = "inv_seq", insertable=false, updatable=false)
    public Integer getInvoiceSeq() {
        return invoiceSeq;
    }

På grund af insertable=false udbyderen vil ikke forsøge at angive en værdi for kolonnen. Du kan nu indstille en passende DEFAULT i databasen, som nextval('some_sequence') og det vil blive hædret. Du skal muligvis genlæse entiteten fra databasen med EntityManager.refresh() efter at have holdt det ved - jeg er ikke sikker på, om persistensudbyderen vil gøre det for dig, og jeg har ikke tjekket specifikationerne eller skrevet et demoprogram.

Den eneste ulempe er, at det ser ud til, at kolonnen ikke kan laves @ NotNull eller nullable=false , da udbyderen ikke forstår, at databasen har en standard for kolonnen. Det kan stadig være NOT NULL i databasen.

Hvis du er heldig, vil dine andre apps også bruge standardmetoden med enten at udelade sekvenskolonnen fra INSERT s kolonneliste eller eksplicit specificering af søgeordet DEFAULT som værdien i stedet for at kalde nextval . Det vil ikke være svært at finde ud af det ved at aktivere log_statement = 'all' i postgresql.conf og søge i loggene. Hvis de gør det, så kan du faktisk skifte alt til gapless, hvis du beslutter dig for det ved at erstatte din DEFAULT med en BEFORE INSERT ... FOR EACH ROW triggerfunktion, der indstiller NEW.invoice_number fra tællerbordet.



  1. Er java.sql.Timestamp tidszonespecifik?

  2. Microsoft SQL Server 2005/2008:XML vs tekst/varchar datatype

  3. 6 grunde til, at Microsoft Access kan hjælpe din virksomhed

  4. Kombiner to kolonner og tilføj til en ny kolonne