Posts tagged procedury
[PL] SQL Server – Kiedy SELECT INTO nie daje rady
Nov 19th
Swego czasu na portalu WSS.pl uczestniczyłem w dyskusji na temat tego, czy należy używać składni SELECT…INTO. Wytaczaliśmy różne tezy: od takich, że kiedyś ta składnia powodowała blokowania, aż po stwierdzenia, że nie jest dobrze tworzyć tabele o nieznanym schemacie. Jednak tak na dobrą sprawę nie podaliśmy żadnego sensownego argumentu przeciw wspomnianej składni.
I tym razem nie podam takiego argumentu, a jedynie pokażę przypadek, w którym SELECT…INTO powoduje błąd i nie można go najzwyczajniej w świecie użyć. Gwoli ścisłości dodam, że w bazach danych (głównie hurtownianych), którymi obecnie się zajmuję, SELECT…INTO jest używane notorycznie i z bardzo dobrym skutkiem (bardzo często z warunkiem w stylu WHERE 1=0, by generować puste struktury, z których później generuję dane – najczęściej moją “ofiarą” pada wówczas arkusz Excela, w którym dane zostały zapisane na nagłówkach kolumn…).
A więc, kiedy SELECT…INTO nie działa?
Weźmy taką procedurę:
USE tempdb; GO CREATE PROCEDURE dbo.SomeProcedure @param int AS IF @param = 1 SELECT database_id AS ID INTO #TMP FROM sys.databases; ELSE SELECT [object_id] AS ID INTO #TMP FROM sys.objects; SELECT * FROM #TMP; GO
Wynkiem uruchomienia powyższego kodu na SQL Server 2005 lub nowszym będzie następujący komunikat błędu:
Msg 2714, Level 16, State 1, Procedure SomeProcedure, Line 7 There is already an object named '#TMP' in the database.
A zatem, jeżeli chcemy wielokrotnie użyć SELECT…INTO w jednej procedurze, by warunkowo założyć tabelę tymczasową o strukturze zależnej od zaistniałego przypadku, nie uda nam się to, choć z logicznego punktu widzenia nie ma żadnych przeszkód, by taki kod kompilował się i uruchamiał się z powodzeniem.
Pamiętam, że wiele razy uczestniczyłem już w dyskusjach na temat wykorzystania tabel tymczasowych w procedurach składowanych i na podobne problemy natknęło się sporo osób. Może się zdarzyć, ale coraz częściej dochodzę do wniosku, że w przypadku zaszywania w procedurze co bardziej skomplikowanej logiki wymagającej wykonywania obliczeń, przeliczeń, agregacji itd. tabele tymczasowe są po prostu niezastąpione z uwagi na łatwość śledzenia kolejnych kroków procesu (debugowanie i odnajdywanie problematycznych miejsc jest dużo łatwiejsze niż w przypadku tworzenia np. mocno zagnieżdżonych podzapytań, CTE czy używania zmiennych tabelarycznych). Oczywiście, nadal trzeba pamiętać o tym, że w środowisku mocno transakcyjnym z jednoczesnym dostępem wielu użytkowników do danych, tabele tymczasowe mogą stanowić źródło licznych problemów wydajnościowych (“tempdb contention” i rekompilacje to chyba najczęstsze problemy).
Tak czy siak, chyba powoli nawracam się na tabele tymczasowe ;-)
[PL] SQL Server 2008 – Statystyki procedur i wyzwalaczy
Aug 2nd
Geneza
Niedawno na forum portalu WSS.pl użytkownik miscu zapytał, w jaki sposób znaleźć datę ostatniego wykonania procedury składowanej na SQL Server 2005. Odpowiedziałem wówczas na jego pytanie, ale potem sprawdziłem, czy w SQL Server 2008 nie dałoby się uzyskać tej informacji nieco łatwiej, bez “grzebania” w kilku widokach i wykonywania podejrzanych złączeń w nadziei, że otrzymany wynik będzie choć trochę przedstawiał prawdę prawdziwą. I owszem, można.
Statystyki procedur
W SQL Server 2008 (i oczywiście w R2 także) dostajemy ciekawe widoki DMV – sys.dm_exec_procedure_stats i sys.dm_exec_trigger_stats. Widoki te pokazują statystyki wykonania odpowiednio procedur składowanych i wyzwalaczy. Oba widoki są widokami globalnymi, tzn. przechowują statystyki dla wszystkich baz danych naraz (nawet dla bazy mssqlresource coś tam się znajdzie). Oba widoki bazują na danych zebranych od ostatniego uruchomienia instancji SQL Server i bazują na tym, co siedzi w cache’u planów wykonania. Napisałem kawałek kodu (pewnie nie ja pierwszy), który wybiera co ciekawsze dane dotyczące procedur składowanych:
SELECT
DB_NAME(s.database_id) AS [database_name],
QUOTENAME(OBJECT_SCHEMA_NAME(s.[object_id], s.database_id)) +
'.' +
QUOTENAME(OBJECT_NAME(s.[object_id], s.database_id)) AS [procedure_name],
s.cached_time,
s.last_execution_time,
s.execution_count,
CONVERT(
decimal(38,2),
(s.execution_count * 1.0 / t.uptime)
) AS executions_per_minute,
CONVERT(
decimal(38,2),
(s.max_elapsed_time * 1.0 / 1000)
) AS max_execution_time_miliseconds,
CONVERT(
decimal(38,2),
((s.total_elapsed_time * 1.0 / 1000) / s.execution_count)
) AS avg_execution_time_miliseconds
FROM sys.dm_exec_procedure_stats AS s
CROSS JOIN (
SELECT
DATEDIFF(minute, sqlserver_start_time, GETDATE()) AS uptime
FROM sys.dm_os_sys_info
) AS t
WHERE s.database_id <> 32767;
Krótki komentarz do kodu:
- wyświetlane dane zawierają:
- database_name – nazwę bazy danych, w której znajduje się procedura,
- procedure_name - dwuczłonową nazwę procedury,
- cached_time – datę zapisania planu procedury w cache’u,
- last_execution_time – datę ostatniego wykonania procedury,
- execution_count – ilość wykonań procedury od ostatniego restartu usługi SQL Servera,
- executions_count_per_minute – ilość wykonań procedury na minutę od ostatniego restartu usługi SQL Servera,
- max_execution_time_miliseconds – maksymalny czas wykonania procedury wyrażony w milisekundach,
- avg_execution_time_miliseconds – średni czas wykonania procedury wyrażony w milisekundach.
- wszystkie czasy w widoku sys.dm_exec_procedure_stats są wyrażone w mikrosekundach, stąd operacje dzielenia przez 1000,
- datę startu instancji wyciągam z widoku DMV sys.dm_os_sys_info (polecam do niego zajrzeć, bo w jednym wierszu zawiera sporo ciekawych informacji),
- w klauzuli WHERE odfiltrowuję obiekty z ukrytej bazy systemowej mssqlresource, bo i tak na nic nam informacje o procedurach z tej bazy :-)
Zamiast filtru eliminującego obiekty z mssqlresource, można pokusić się o filtrowanie względem bazy, w której chcemy optymalizować procedury:
… WHERE s.database_id = DB_ID('MojaBazaDanych');
Przykładowy fragment wyniku powyższego zapytania:
Znajdź nieużywane procedury
Widok sys.dm_exec_procedure_stats może się też przydać w scenariuszu, w którym w bazie danych zawierającą dużą ilość procedur chcemy odnaleźć te procedury, które nie są wykorzystywane (nie zostały użyte od ostatniego uruchomienia serwera). Do tego celu można użyć na przykład takiego zapytania (tu – szukam procedur nieużywanych w bazie AdventureWorks2008R2):
USE AdventureWorks2008R2; -- tu wstaw nazwę Twojej bazy danych GO SELECT QUOTENAME(SCHEMA_NAME(p.[schema_id])) + '.' + QUOTENAME(p.name) AS unused_procedure FROM sys.procedures AS p INNER JOIN sys.sql_modules AS m ON p.[object_id] = m.[object_id] LEFT JOIN sys.dm_exec_procedure_stats AS s ON s.database_id = DB_ID() AND p.[object_id] = s.[object_id] WHERE s.[object_id] IS NULL AND m.is_recompiled = 0; GO
Jeżeli serwer nie był restartowany przez odpowiednio długi czas i wiesz, że zostały przeprowadzone przypadki biznesowe wykorzystujące procedury, możesz założyć, że procedury wylistowane przez powyższe zapytanie wymagają sprawdzenia, czy są do czegokolwiek potrzebne (od polityki firmy i solidności programistów / administratorów zależy, jak często zdarzy się, że ktoś założy na bazie produkcyjnej tymczasowe obiekty, a następnie zapomni po sobie posprzątać). Celowo w powyższym zapytaniu sięgam do widoku sys.sql_modules, gdyż jest tam informacja o tym, czy procedura została utworzona z opcją WITH RECOMPILE (kolumna is_recompiled). A jeśli tak jest, jej planu nie znajdziesz w cache’u i nie możesz się sugerować jej brakiem w widoku sys.dm_exec_procedure_stats.
Słowo o wyzwalaczach
Analogiczne informacje można uzyskać także dla wyzwalaczy. I to nie tylko dla wyzwalaczy DML, ale także DDL (w tym dla logon triggerów). Do wykorzystania oprócz DMV sys.dm_exec_trigger_stats są widoki:
- sys.triggers – zwraca metadane wyzwalaczy na poziomie bieżącej bazy danych (DML i DDL),
- sys.server_triggers – zwraca metadane wyzwalaczy DDL na poziomie serwera (w tym logon triggerów).

Nazywam się Paweł Potasiński i pracuję w polskim oddziale Microsoft w dziale Small and Midmarket Solutions & Partners (SMS&P) jako Partner Technology Advisor.




