19.09.2016, Vladimír Klaus, navštíveno 4256x

ASP.NET/C#
SQL Server

Po delší době jsem se vrátil k jedné ASP.NET aplikaci a použil dlouho nepoužívané tlačítko. Jaké bylo překvapení, že po cca 15 vteřinách to skončilo chybovou hláškou, kterou jsem v životě neviděl.

Co s chybou "Časový limit vypršel před získáním připojení od fondu", obr. 1

Začal jsem pátrat a to především po anglickém originálu hlášky. Našel jsem ji na docela zajímavém webu.

Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.

Dalším pátráním a vyřazením slepých uliček jsem narazil na to, že by to mohlo být příliš dlouho trvajícím dotazem. To ale nebylo moc pravděpodobné, takže hned druhý důvod bylo to, že aplikace má ve výchozí podobě nastaveno v connection stringu "Max Pool Size=100". A že když se tento počet překročí, může dojít k nějaké takové chybě. A bylo tomu tak!

Takže co se u mě stalo:

  • na jedné stránce jsem potřeboval výpis max. 100 nejnovějších položek, tak jsem normálním SELECTem získal 100 řádek
  • každou řádku jsem ale potřeboval analyzovat a vypisovat (do HTML) dle řady podmínek, ale také voláním dalších 2 dotazů do databáze
  • tyto dotazy byly extrémně krátké a extrémně rychlé (načtení něčeho z číselníku apod.), takže žádný problém s timeoutem
  • jenomže se kvůli vygenerování této stránky volaly 200x

Za normálních okolností se připojení k databázi samo uvolňuje. Jenomže v tomto případě tomu tak nebylo, a tak jsem téměř okamžitě vyčerpal výchozí limit 100 připojení, pak aplikace požadovala další a po 15 vteřinách to vzdala uvedenou hláškou.

A proto stačí přidat jeden jediný příkaz - db.Close() a je vyřešeno.

public static string Stav2Popis(string aStav) {
	var db = Database.Open("Data");
	var zaznam = db.QuerySingle
		("SELECT Popis FROM Stavy WHERE Stav=@0", aStav);
	db.Close();
	return zaznam != null ? zaznam.Popis : "(nenalezeno!)";
}

Zdá se vám to jednoduché? No ano, to je! Ale když bez uzavírání připojení fungujete více než 5 let a když zavírání v podstatě v žádném příkladu neuvidíte, tak vás tato potřeba překvapí. Ale znovu opakuji - problém byl tím rychlým opakovaným voláním dotazů. Pro běžné použití, tedy jeden dotaz a pak dlouho nic, není třeba nic měnit a kód upravovat.

A proč k této chybě nikdy předtím nedošlo? Opět zcela jednoduchá odpověď - nikdy v minulosti nebylo na ostrém serveru tolik dat, aby ten hlavní SELECT vrátil více než pár desítek řádek. Zkrátka jsem se zřejmě vždy do limitu vešel.

Zdá se vám velmi hloupé, se takto databáze dotazovat? A že byste si to nejprve načetli do nějakého pole a pak použili, už bez zbytečné práce s databází? Ano, samozřejmě! Jenže na úplném začátku jsem nevěděl, že budu funkci takto intenzivně využívat a pak jsem teď velmi rád, že k tomu došlo - poučil jsem se a mohu o některých přístupech zase přemýšlet trochu jinak. :-)