В това продължение на моята поредица „настройване на производителност на коляното“ бих искал да обсъдя четири често срещани проблема, които виждам при използването на временни таблици. Всеки един от тези проблеми може да осакати натоварването, така че си струва да знаете за тях и да ги търсите във вашата среда.
Проблем 1:Използване на временни таблици, където не са необходими
https://www.flickr. com/photos/tea_time/3890677277/Временните таблици имат различни приложения (вероятно най-често срещаната е да съхраняват междинен набор от резултати за по-късна употреба), но трябва да запомните, че когато въвеждате временна таблица в заявка, прекъсвате потока от данни през процесор на заявки.
Мислете за популацията на временна таблица като за твърд стоп, тъй като има заявка (нека я наречем производител) за генериране на междинния набор от резултати, който след това се съхранява във временната таблица в tempdb и след това следващата заявка (нека извикаме потребителят) трябва да прочете отново данните от временната таблица.
Често съм откривал, че някои части от работното натоварване всъщност се представят по-добре, когато временната таблица е напълно премахната, така че данните преминават от частта производител на заявката към частта на потребителя на заявката, без да се налага да се запазват в tempdb, а оптимизаторът на заявки може да създаде по-оптимален общ план.
Може би сега си мислите, "така че защо някой би използвал временна таблица, ако това прави нещата по-бавни?" – и правилно! В такива случаи открих, че използването на временна таблица е станало институционализирано в екипа за разработка; някой установи, че използването на временна таблица повишава производителността преди много години, така че временните таблици станаха изборът по подразбиране за дизайн.
Това може да е трудно да се промени, особено ако имате старши разработчик или мениджър, който е убеден, че винаги трябва да се използват временни таблици. Най-простото нещо, което трябва да опитате, е да изберете скъпа заявка (например, продължителна или такава, която се изпълнява много пъти в секунда) и да премахнете една или повече от временните таблици, за да видите дали производителността се увеличава без тях. И ако е така, ето вашето доказателство, за да покажете непримиримите!
Проблем 2:Липса на филтриране при попълване на временни таблици
Дори и да не можете да премахнете временна таблица, може да успеете да подобрите драстично производителността, като се уверите, че кодът, който попълва временната таблица, правилно филтрира данните, извлечени от таблиците с източник.
Загубих броя на пъти, когато видях временна таблица да се попълва с код, който започва като SELECT *
, включва няколко неограничаващи присъединявания и няма клауза WHERE, а след това по-късната заявка, която използва временната таблица, използва само няколко колони и има клауза WHERE за значително намаляване на броя на редовете.
Спомням си един случай, когато временна таблица в съхранена процедура събираше данни за 15 години от основната база данни и след това се използваха само данните за текущата година. Това многократно караше tempdb да нараства, докато не свърши място на дисковия том и съхранената процедура щеше да се провали.
Всеки път, когато попълвате временна таблица, използвайте само колоните на изходната таблица, които са необходими, и само редовете, които са необходими – т.е. натиснете предикатите на филтъра нагоре във временния код на популацията на таблицата. Това не само ще спести място в tempdb, но също така ще спести много време от това да не се налага да копирате ненужни данни от таблицата източник (и евентуално да премахне необходимостта от четене на изходни страници от база данни на първо място).
Проблем 3:Неправилно индексиране на временна таблица
Точно както при обикновените таблици, трябва да създавате само индексите, които действително ще бъдат използвани от по-късния код на заявка, за да подпомогнат изпълнението на заявката. Виждал съм много случаи, в които има неклъстериран индекс за колона на временна таблица, а индексите с една колона, които са избрани без анализ на по-късния код, често са доста безполезни. Сега комбинирайте безполезни неклъстерирани индекси с липса на филтриране при попълване на временната таблица и ще получите рецепта за огромно раздуване на tempdb.
Освен това, като цяло, е по-бързо да се създават индекси, след като таблицата е била попълнена. Това дава допълнителен бонус, че индексите ще имат точни статистически данни, което може допълнително да помогне на заявката, тъй като оптимизаторът на заявки ще може да направи точна оценка на мощността.
Наличието на куп неклъстерирани индекси, които не се използват, губи не само дисково пространство, но и времето, необходимо за създаването им. Ако това е в код, който се изпълнява често, премахването на тези ненужни индекси, които се създават всеки път, когато кодът се изпълнява, може да има значителен ефект върху цялостната производителност.
Проблем 4:tempdb Latch Contention
Доста обичайно е да има блокиращо тесно място в tempdb, което може да бъде проследено до временна употреба на таблица. Ако има много едновременни връзки, изпълняващи код, който създава и пуска временни таблици, достъпът до битовите карти за разпределение на базата данни в паметта може да се превърне в значително затруднение.
Това е така, защото само една нишка в даден момент може да променя битова карта за разпределение, за да маркира страници (от временната таблица) като разпределени или освободени, и така всички останали нишки трябва да чакат, намалявайки пропускателната способност на работното натоварване. Въпреки че има временен кеш на таблицата от SQL Server 2005, той не е много голям и има ограничения за това кога временната таблица може да се кешира (например само когато е по-малка от 8MB).
Традиционните начини за заобикаляне на този проблем са били използването на флаг за проследяване 1118 и множество tempdb файлове с данни (вижте тази публикация в блога за повече информация), но друго нещо, което трябва да имате предвид, е да премахнете изцяло временните таблици!
Резюме
Временните таблици могат да бъдат много полезни, но много лесно и често се използват неправилно. Всеки път, когато пишете (или преглеждате код), който използва временна таблица, имайте предвид следното:
- Тази временна таблица Наистина ли е необходима ?
- Кодът, който попълва таблицата, използва правилното филтриране да ограничите размера на временната таблица?
- Индексите създават ли се след популация на таблица (като цяло) и ли индексите се използват с по-късен код?
Пол Уайт има няколко страхотни публикации (тук и тук) относно временното използване на обекти и кеширане, които също препоръчвам да прочетете.
И последно нещо, ако решите да не използвате временна таблица, не я заменяйте просто с таблична променлива, общ израз на таблица или курсор (всички те са често срещани начини, по които хората се опитват да „оптимизират“ временна таблица) – намерете най-ефективния начин за (пре)написване на кода – няма отговор „един размер за всички“.
До следващия път, щастливо отстраняване на неизправности!