ADO.NET - Connection Pooling (Best Practices 2)

Tags:    .net
Skrevet af Bruger #293 @ 23.03.2006
Connection pooling har været omkring i mere end 7 år og er slået til som standard, alligevel er der mange der ikke rigtig ved hvilken konsekvens connection pooling har når man tilgår en database. Dette vil jeg forsøge at råde en smule bod på i denne artikel.

Baggrund



Med connection pooling får din applikation tildelt en forbindelse fra en pulje af pre-åbnede forbindelser, klar til brug. Forbindelserne er inddelt i grupper baseret på bl.a. din connection string. Når du ønsker at få tildelt en forbindelse tjekker connection poolingen således om der findes en aktiv forbindelse i den gruppe af connections der matcher din connection string. Eksisterer der en får du blot denne tildelt ellers oprettes en ny aktiv forbindelse som den så returnerer til din applikation. Når din applikation er færdig med den pågældende forbindelse returneres den igen til poolen, vel og mærke i aktiv tilstand - klar til senere brug. Forbindelser timer typisk ud i løbet af 4-8 minutter (kan indstilles) hvis de ikke har været i brug. En pulje kan som standard indeholde op til 100 forbindelser og ryger man op i det loft bør man enten overveje sit systemdesign eller også har man en connection leak - mere om sidstnævnte senere.

Fordelen er klar, din applikation skal ikke bruge ressourcer på at åbne og lukke forbindelser hver gang en handling skal udføres og ej heller holder din applikation på vigtige ressourcer mens forbindelsen ikke benyttes, hvilket f.eks. vil være konsekvensen ved at åbne forbindelsen ved start af applikationen og frigive den ved lukning. Sidstnævnte ses desværre alt for ofte, i både WinForms og ASP.NET applikationer (det grelle eksempel her er at åbne den i session_start og lukke den i session_end). Det skyldes den udprægede misforståelse at man faktisk åbner og lukker en forbindelse ved kald til SqlConnection.Open() og SqlConnection.Close(), hvor der reelt er tale om at de henholdsvis trækker en connection og returnerer den fra/til poolen (medmindre man selvfølgelig har slået connection pooling fra).

Oprettelse af en ny pulje i connection poolen



Som jeg i forrige afsnit var lidt inde på så inddeles forbindelserne i puljer bl.a. baseret på din connection string.
Din connection string skal således være identisk ved hver forespørgsel på en connection for at du kan modtage en aktiv forbindelse, ellers vil du istedet opnå at der oprettes 1 pulje med 1 aktiv forbindelse for hver forespørgsel - hvilket jo lidt ødelægger formålet.

Når jeg siger "bl.a. din connection string" så skyldes det at der er et par faktorer mere der har indflydelse, af dem jeg er bekendt med er følgende:

- Din connection string - Denne skal være identisk med tidligere forespørgsler for at forbindelsen kategorieseres i samme pulje. Selv en ændring af UID (User ID) vil resultere i at der oprettes en selvstændig pulje til disse forbindelser.
- Distribuerede transaktioner - Hver transaction scope får tildelt sin egen pulje.
- Process - Hver process (eksempelvis program, komponent, ASP.NET website) får sin egen pulje.

Faldgruber



Som tidligere nævnt er en af de mest udbredte faldgruber at folk ganske enkelt ikke gør brug af connection pooling og istedet holder forbindelsen i live under en hel bruger-session.
Lad mig derfor starte med at understrege en guideline: En forbindelse skal altid trækkes fra poolen i sidste øjeblik inden den skal anvendes og returneres hurtigst muligt igen!

Med det slået fast er næste faldgrube connection leaks. Lad mig her starte med endnu en guideline: Stol aldrig på at garbage collectoren skal returnere din forbindelse til poolen!
Det kan tage lang tid før en garbage collection udføres og det vil derfor konflikte med førnævnte guideline der siger at en forbindelse skal returneres hurtigst muligt.

Den sidste faldgrube jeg vil komme ind på her drejer sig om exceptions.

Fold kodeboks ind/udKode 



Ovenstående er ganske gyldig kode og lever op til vores 2 tidligere guidelines. Alligevel er der i ovenstående en potentiel fare for en connection leak.
Problemet opstår hvis command.ExecuteNonQuery(); smider en exception. I dette tilfælde vil conn.Dispose(); aldrig blive kaldt og forbindelsen vil derfor ikke blive returneret til connection poolen (før garbage collectoren engang forbarmer sig).

Den korrekte løsning er derfor altid at wrappe sådan kode i en try-finally blok:

Fold kodeboks ind/udKode 



Nu tænker du måske nok at ovenstående godt nok er en grusom masse kode hver gang man skal udføre en lille handling og selv om man selvfølgelig kan bygge sit eget lille framework der håndterer mange af disse ting for en, så vil jeg dog lige slutte af med at illustrere en lidt simplere version af ovenstående kode, der istedetfor try-finally anvender det lidt fiksere using keyword i C#.

Fold kodeboks ind/udKode 


Årsagen til at det kan gøres så simpelt som ovenstående skyldes at using keywordet tager et IDisposable objekt og implicit udfører en try-finally hvor Dispose på objektet automatisk kaldes i finally - syntaktisk sukker er en herlig ting :)

Det skal bemærkes at jeg i ovenstående eksempler aldrig kalder conn.Close() andet end implicit via conn.Dispose(), dette er for det første for at illustrere hvor få linier koden kan komme ned på, men også fordi det reelt er unødvendigt at kalde Close() før Dispose(). For gennemskuelighed i et reallife projekt med flere udviklere kan det dog anbefales altid at lade et kald til Open() afsluttes med et kald til Close() for at der ikke skal herske tvivl om forløbet.

Konklusion



Connection pooling er ikke noget man kommer sovende til omend ADO.NET håndterer det praktiske.
Man er nød til at have connection pooling med i sine overvejelser når man skriver sin kode.

En opsummering af ovenstående:

- En forbindelse skal altid trækkes fra poolen i sidste øjeblik inden den skal anvendes og returneres hurtigst muligt igen!
- Stol aldrig på at garbage collectoren skal returnere din forbindelse til poolen!
- Exceptions kan skabe connection leaks, benyt derfor altid en try-finally blok eller den lidt mere elegante using løsning.

Referencer



Swimming in the .NET Connection Pool
En del af den grundlæggende viden om connection pooling stammer fra denne eminente artikel. Mens jeg har forsøgt at forklare det basale i indledningen til denne post, kan det kraftigt anbefales at gennemlæse denne artikel hvis man vil gå mere i dybden:

http://www.windowsitpro.com/SQLServer/Article/ArticleID/38356/SQLServer_38356.html
(Er desværre blevet til en betalings-artikel)



Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (8)

User
Bruger #2730 @ 23.03.06 14:13
Nice!!!

Betyder det så at der altid (implicit) benyttes connectionpooling?

Skal jeg ikke selv sætte noget op i databasen, på min connection eller hva?

Hvordan styrer jeg Timeout på min pool?

Jeg kunne naturligvis bare læse artiklen, meeen alligevel :-)
User
Bruger #293 @ 23.03.06 19:17
Brian: Ja, ADO.NET anvender altid connection pooling, medmindre du specifikt slår det fra i connection stringen.
Det er en meget rar detalje at være klar over, ellers kan man komme til at dumme sig ret så meget i et forsøg på at oprette færest mulige connections i sin applikation/website :)

Der er 2 connection string parametre der er interessante i forbindelse med Timeouts. "Connection Lifetime" og "Connection Timeout".

Førstnævnte er den der returnerer en connection til poolen igen hvis man ikke har benyttet den i det specificerede livstidsinterval. Men eftersom guidelinen siger at man altid skal returnere den så hurtigt som muligt efter brug, burde den ikke være relevant - medmindre man har et website med en del connection leaks der kræver en quickfix.

Connection Timeout angiver hvor længe du vil forsøge at trække en ny forbindelse fra poolen, default er 15 sekunder. Har typisk også kun relevans i en applikation med connection leaks da man her ofte vil ramme loftet og dermed ikke være i stand til at trække en ny connection.
User
Bruger #5779 @ 24.03.06 10:07
God artikel :D
User
Bruger #298 @ 27.03.06 19:07
Glimrende artikel...
User
Bruger #2193 @ 04.04.06 00:14
At Connection-pool kun har 7 år på bagen har jeg svært ved at tro helt på. Mon vi snakker i MS sammenhæng her?

Bortset fra det kan jeg ikke komme på noget at brokke mig over.
Rigtig fin artikel, både for begyndere og de lidt øvede :)
User
Bruger #293 @ 04.04.06 23:35
Der står "Connection pooling har været omkring i mere end 7 år".

Det der menes er at connection pooling har været alment udbredt i mere end 7 år, ikke at det kun har eksisteret i denne periode :)
User
Bruger #714 @ 09.04.06 13:36
Herlig artikel, relevant emne og skrevet i et let forståeligt sprog! Lækkert flere af dem tak!
User
Bruger #815 @ 07.07.06 16:42
Herlig artikel, og så skal der kodes lidt.. :P
Du skal være logget ind for at skrive en kommentar.
t