SQL Server
[PL] SQL Server – Jak optymalizator robi ze mnie głupca
May 31st
Dawno, dawno temu dowiedziałem się z bloga Tibora Karasziego (SQL Server MVP), że optymalizator może wykorzystać obiekty constraint, takie jak CHECK czy FOREIGN KEY (klucz obcy) do optymalizacji określonych klas zapytań.
Jakiś czas temu próbowałem pokazać koledze z firmy, jak to działa i przy okazji dowiedziałem się ciekawej rzeczy, na którą dotąd nie natrafiłem. A w zasadzie, można powiedzieć, normalnie aż zgłupiałem :-)
Na początek weźmy tabelę:
USE tempdb; GO IF OBJECT_ID('dbo.Employee') IS NOT NULL DROP TABLE dbo.Employee; CREATE TABLE dbo.Employee ( EmployeeId int IDENTITY(1,1) NOT NULL PRIMARY KEY, FirstName varchar(50) NOT NULL, LastName varchar(50) NOT NULL, Income money NOT NULL DEFAULT (0) ); GO ALTER TABLE dbo.Employee ADD CONSTRAINT CK_Employee_Income CHECK (Income >= 0); GO INSERT INTO dbo.Employee (FirstName, LastName, Income) SELECT NEWID(), NEWID(), RAND(CHECKSUM(NEWID())) * v1.number + v2.number FROM master.dbo.spt_values AS v1, master.dbo.spt_values AS v2 WHERE v1.type = 'P' AND v2.type = 'P' AND v1.number BETWEEN 1 AND 1000 AND v2.number BETWEEN 1 AND 1000; GO
Tabela dość typowa. I zawiera milion rekordów. To, co ważne, to fakt, że założyłem w niej warunek ograniczający możliwe wartości kolumny Income (muszą być nieujemne). Świeżo założony w powyższy sposób warunek CHECK jest “trusted” (patrz wpis na blogu Tibora). Weźmy teraz zapytanie:
SELECT * FROM dbo.Employee WHERE Income < 0; GO
Plany wykonania na mojej maszynie i przy konfiguracji serwera z MAXDOP = 0 wygląda u mnie tak:
Hę? Spodziewałem się, że zobaczę operator Constant Scan i… tyle! A tymczasem jakiś skan, z jakimś parallelismem… Horror!
Pobawiłem się trochę tym zapytaniem. Pierwsza myśl – wyeliminować parallelism:
SELECT * FROM dbo.Employee WHERE Income < 0 OPTION (MAXDOP 1); GO
Plan wykonania:
Noooo, to lubimy :-) Żadnego zbędnego zaglądania do tabeli, po prostu od razu na podstawie zaufanego warunku CHECK generowana jest odpowiedź, że nie ma rekordów spełniających warunek z zapytania.
A teraz deser – przyznaję, że sam tego nie wymyśliłem (zbyt fikuśne :-)), choć testowałem różne śmieszne warianty zapytań (m.in. dodawałem grupowania po wszystkich kolumnach tabeli – pomagało i wymuszało Constant Scan). Maciek Pilecki, z którym ten problem przedyskutowałem i który, podobnie jak ja, ma wrażenie, że coś tu nie gra, wypróbował taki kwiatek:
SELECT * FROM dbo.Employee WHERE Income < 0 OPTION (MAXDOP 0); GO
Efekt? Sami sprawdźcie, ale gwarantuję pełne zaskoczenie :-) Oczywiście, Constant Scan! I o so chosi?
Wygląda na to, że optymalizator pozostawiony samemu sobie i stojący przed “trudnym” wyborem (nie wpisując hinta powodujemy, że optymalizator ma do dyspozycji wszystkie możliwe ścieżki optymalizacji zapytania) robi głupotę. A może to nowy bug? Ale plan dla zapytania z MAXDOP = 0 powoduje już moją konsternację. Trzeba będzie z kimś z grupy produktowej o tym porozmawiać, bo bardzo nie lubię tłumaczyć się przed kolegami, którym demonstruję “niby dobrze znane” triki optymalizacyjne, a tymczasem optymalizator krzyżuje moje plany i wychodzę na głupca :-)
Wszelkie przemyślenia własne na temat powyższego mile widziane. Moje przemyślenie – nigdy nie bądź niczego pewnym na 100% bez sprawdzenia organoleptycznie :-)
Dodam, że przełączenie MADXOP = 1 na poziomie serwera nie zmienia sytuacji – dalej mamy skan całej tabeli, jeśli nie dodamy hinta lub nie skomplikujemy zapytania! O co tutaj chodzi??? :-)
I na sam koniec – kupa śmiechu. Jaki jest bodaj najprostszy możliwy hint wymuszający Constant Scan w omawianym przypadku? Opadają mi ręce, gdy pomyślę o mojej SQL-owej niewiedzy…
SELECT * FROM dbo.Employee WHERE Income < 0 AND 1=1; GO
[EDIT] Jak słusznie zauważył Patrik Třeštik komentując ten wpis, problem jest najpewniej spowodowany przez parametryzację zapytania (i zarazem zjawisko parameter sniffing). Jakimś cudem zero w warunku WHERE przestaje być stałą i optymalizator zaczyna traktować je jak wartość nieznaną, a więc jednocześnie nie może porównać jej do wartości zaszytej w ograniczeniu CHECK. Tylko powstaje pytanie – po co ta parametryzacja? :-) [/EDIT]
[PL] SQL Server – {Workbench} Funkcje i ich “gotchas”
May 4th
Od jakiegoś czasu chodził za mną pomysł, żeby publikować na blogu obszerne fragmenty kodu, które wraz z komentarzami w nich zawartymi opisywałyby się same. Pomysł zaczerpnąłem z serii, którą dawno temu śledziłem na portalu Simple Talk (przykład). Dzisiaj pierwszy workbench na moim blogu – poświęcony pułapkom w systemowych funkcjach skalarnych (kod trzymam od dawna, więc pomyślałem, że czas go opublikować). Prawie na pewno nie będzie to ostatni workbench, jaki opublikuję. A gdyby komuś przyszło do głowy, co by można było dopisać do listy poniższych “gotchas”, śmiało dodawajcie w komentarzach albo ślijcie na maila (najlepiej od razu w takiej postaci, jak poniżej). Creditsy gwarantowane :-)
/* Workbench: Funkcje i ich "gotchas" Cel: Pokazanie pułapek ukrytych w systemowych funkcjach skalarnych w SQL Server. Założenie: Znajomość przedstawionych funkcji i umiejętność znalezienia pomocy do nich w BOL Uwagi: kod zawiera tylko "gotchas", nie podaję gotowych obejść problemów */ ------------------------------------------------------------ -- Funkcja : DATEDIFF -- Gotcha : Niedziela zawsze pierwszym dniem tygodnia -- Rozwiązanie : Własna funkcja ------------------------------------------------------------ -- Niedziela pierwszym dniem tygodnia SET DATEFIRST 7 -- Wynik: oczekiwane 0 SELECT DATEDIFF(week, '20110501', '20110502') -- Poniedziałek pierwszym dniem tygodnia SET DATEFIRST 1 -- Wynik: nieoczekiwane 0 SELECT DATEDIFF(week, '20110501', '20110502') ------------------------------------------------------------ -- Funkcja : ISNULL -- Gotcha : Typ danych narzucany przez pierwszy parametr -- Rozwiązanie : Jawna konwersja lub użycie COALESCE zamiast ISNULL ------------------------------------------------------------ -- Wynik: Ala m - bo typ wynikowy to char(5) SELECT ISNULL(CONVERT(char(5), NULL), 'Ala ma kota') ------------------------------------------------------------ -- Funkcja : ISNUMERIC -- Gotcha : Nie zawsze mówi prawdę -- Rozwiązanie : Własna funkcja (CLR?) ------------------------------------------------------------ -- Wynik: 0 i 0, czyli '' nie jest liczbą, -- ale daje się rzutować na typ liczbowy SELECT ISNUMERIC(''), CONVERT(int, '') ------------------------------------------------------------ -- Funkcja : LEN -- Gotcha : Przed zliczaniem znaków ucina końcowe spacje -- Rozwiązanie : Własna funkcja ------------------------------------------------------------ -- Wynik: 3 (zamiast 5) SELECT LEN('abc ') ------------------------------------------------------------ -- Funkcja : QUOTENAME -- Gotcha : Zwraca NULL dla parametrów dłuższych niż 128 znaków -- Rozwiązanie : Własna funkcja ------------------------------------------------------------ -- Wynik: NULL SELECT QUOTENAME(REPLICATE('a', 129)) ------------------------------------------------------------ -- Funkcja : REPLICATE -- Gotcha : Typ danych prawie narzucany przez pierwszy parametr -- Rozwiązanie : Jawna konwersja pierwszego parametru ------------------------------------------------------------ -- Wynik: 7998, bo kolejna trójka nie mieści się w 8000, -- a typ varchar(max)/nvarchar(max) musi być wskazany jawnie SELECT LEN(REPLICATE(CONVERT(char(3), 'abc'), 3000)) ------------------------------------------------------------ -- Funkcja : SERVERPROPERTY -- Gotcha1 : Dla "nieznanej" wartości parametru zwraca NULL -- Gotcha2 : Zwraca wartość typu sql_variant (jak inne funkcje *PROPERTY) -- Rozwiązania : Uważać na parametr + jawna konwersja wyniku ------------------------------------------------------------ -- Wynik: NULL i nazwa_maszyny SELECT SERVERPROPERTY('MachinName'), SERVERPROPERTY('MachineName') -- Wynik: Błąd -- Msg 402, Level 16, State 1, Line 1 -- The data types varchar and sql_variant are incompatible in the add operator. SELECT 'Nazwa maszyny: ' + SERVERPROPERTY('MachineName') ------------------------------------------------------------ -- Funkcja : STR -- Gotcha : Czasem zwraca "gwiazdki" :-) -- Rozwiązania : Podawać dwa parametry lub nie używać STR ------------------------------------------------------------ -- Wynik: ********** i 12345678901 -- Pierwsze wyrażenie zwraca wartość typu char(10), -- a gwiazdki to wynik zwracany przez STR przy przekroczeniu -- długości określonej przez drugi opcjonalny parametr (domyślnie 10) SELECT STR(12345678901), STR(12345678901, 11)
[PL] SQL Server – Czy potrzebujemy master.dbo.sysprocesses?
Apr 29th
Na portalu connect,microsoft,com jednym ze zgłoszonych błędów jest ten zgłoszony przez Tony’ego Rogersona pt. Deprecation of sysprocesses – DMV’s doesn’t fully replace all columns. Od momentu, gdy w dokumentacji SQL Servera w opisie widoku sysprocesses (tak, to jest widok, ale piszą o nim, jakby to była tabela, jak za czasów SQL Servera 2000) pojawił się zapisek:
This feature will be removed in a future version of Microsoft SQL Server. Avoid using this feature in new development work, and plan to modify applications that currently use this feature.
rozgorzały dyskusje na temat słuszności posunięcia Microsoftu zmierzającego w kierunku wycięcia sysprocesses z przyszłych wersji SQL Servera. Głównym zarzutem zawsze było to, że widoki DMV, które Microsoft rekomenduje zamiast sysprocesses (konkretnie są to: sys.dm_exec_connections, sys.dm_exec_sessions, sys.dm_exec_requests) nie umożliwiają zwrócenia tych samych informacji, które można uzyskać odpytując sysprocesses. W szczególności nie można z ich pomocą dowiedzieć się, jakie sesje są podłączone do konkretnej bazy danych (np. w celu zamknięcia określonych połączeń poleceniem KILL), ponieważ nawet, jeśli w którymś z nich (sys.dm_exec_requests) pojawia się kolumna zawierająca identyfikator bazy danych, to niestety sensowne identyfikatory pojawiają się w niej tylko, gdy użytkownik używa obiektu proceduralnego, a nie pojawiają się w przypadku używania zapytań ad-hoc. Smaczku sprawie dodaje fakt, że Microsoft sam nadal wykorzystuje widok sysprocesses w zapytaniach zadawanych przez narzędzia najnowszych nawet wersji SQL Servera (np. Activity Monitor w SSMS 2008 R2 używa tego widoku do uzyskiwania listy sesji).
Paul White (aka SQLKiwi), świeżo upieczony SQL Server MVP i posiadacz arcyciekawego bloga (polecam!), podał obejście problemu z uzyskaniem przy użyciu DMV listy sesji podłączonych do konkretnej bazy danych:
SELECT request_session_id FROM sys.dm_tran_locks WHERE resource_type = N'DATABASE' AND request_mode = N'S' AND request_status = N'GRANT' AND request_owner_type = N'SHARED_TRANSACTION_WORKSPACE' AND resource_database_id = DB_ID(N'AdventureWorks');
Czy powyższe zapytanie zawsze zadziała poprawnie? Dla baz użytkownika, wg wszelkich przesłanek, tak. Testowałem różne warianty, poziomy izolacji i zapytanie zaproponowane przez Paula daje dobry wynik. Oczywiście, ciśnie się na usta pytanie, czemu nie można tego wyłuskać za pomocą trzech wspomnianych DMVs z rodziny sys.dm_exec_*. To pytanie do Microsoftu, na które chyba nikt w Redmond nie potrafi sensownie odpowiedzieć :-)
Analogiczne zapytanie z użyciem sysprocesses zwracające listę sesji podłączonych do bazy danych:
SELECT spid FROM master.dbo.sysprocesses WITH (NOLOCK) -- asekuracyjny NOLOCK WHERE spid > 50 -- choć ponoć spid > 50 nie zawsze oznacza sesję użytkownika AND dbid = DB_ID(N'AdventureWorks');
A teraz zagadka na długi weekend. Dla jakich baz zapytanie Paula nie powie prawdy (nie pokaże pełnej listy podłączonych sesji, bo nie wykryje blokad typu SHARED_TRANSACTION_WORKSPACE)? Odpowiedzi możecie wpisywać w komentarzach do tego wpisu. Podpowiem, że akurat w przypadku tych baz raczej nikomu nie przyjdzie do głowy wycinać podłączonych sesji.
Miłego długiego majowego weekendu życzę :-)
[PL] SQL Server – Moja nierówna walka z Resource Governorem
Apr 17th
W zeszłym tygodniu podjąłem próbę zaprzęgnięcia Resource Governora (dalej zwanego RG) w służbie moim potrzebom administracyjnym. Konkretnie, chodziło o to, by dla dobrze określonego zbioru kroków z zadań (jobów) SQL Server Agenta narzucać odpowiedni MAXDOP.
Zacząłem od zbadania, czy w ogóle jest o co walczyć – to znaczy, czy jestem w stanie namierzyć właściwy krok zadania Agenta po wartości zwracanej przez funkcję APP_NAME (nazwa aplikacji). Okazało się, że tak, choć droga jest ciut straszna. W momencie, gdy wykonywany jest job, każdy krok to nawiązanie od nowa połączenia z SQL Serverem (logout + login). Za każdym krokiem zmienia się nazwa aplikacji, zawsze na coś podobnego do:
SQLAgent – TSQL JobStep (Job 0×4935DE6F94B48045822A472E80BAF8F0 : Step 1)
Z powyższego dało się wycągnąć nazwę joba (wyboldowane binaria to zrzutowany na varbinary identyfikator joba) oraz nazwę kroku (gdy znamy identyfikator joba wyciągamy z tabeli msdb.dbo.sysjobsteps krok o odpowiednim numerze wziętym z nazwy aplikacji – wyboldowany numer na końcu nazwy aplikacji).
Napisałem na szybko funkcję klasyfikującą (kopiując ją z jakiegoś artykułu i zmieniając tylko warunek – na stałe wpisałem zwracaną nazwę workload grupy, do której moja testowa instancja miała przypisywać wszystkie połączenia), skonfigurowałem pulę i workload grupę (MAXDOP=4) i przetestowałem całe rozwiązanie. Działało pięknie i rozkoszowałem się planami wykonania z poziomem zrównoleglenia dokładnie równemu 4 :-)
Teraz pozostało już tylko dopisać w funkcji klasyfikującej logikę – odwołania do msdb w celu wydobycia odpowiednich danych, które pozwoliłyby stwierdzić, czy połączenie jest związane z właściwym krokiem joba, czy też nie.
I tu RG mnie powalił… Zupełnie nie skojarzyłem, że każda funkcja klasyfikująca RG musi mieć w definicji opcję WITH SCHEMABINDING… A co za tym idzie? Nie można odwołać się do żadnego obiektu używając większej ilości członów jego nazwy niż dwa! Nie da się odpytać tabeli z innej bazy danych niż master! Zonk… Próbowałem na różne sposoby, ale nie dało się oszukać SCHEMABINDINGu (widok w bazie master?- źle, bo musi być z opcją SCHEMABINDING, więc też nie odwołam się do msdb; OPENROWSET? – źle, bo nie można użyć w funkcji…). Co mi zostało? Drut. Drut w postaci cyklicznego kopiowania do bazy master danych z tabeli msdb.dbo.sysjobsteps. To rozwiązanie nie przypadło mi jednak do gustu :-)
Wszystko rozumiem. Rozumiem, że RG nie pozwala ograniczać bufora danych, że operuje per połączenie, że niektóre limity są honorowane wtedy, gdy inna grupa potrzebuje zasobów. Świetnie. Ale po co u licha w funkcji klasyfikującej ten nieszczęsny SCHEMABINDING, który z całkiem obiecująco wyglądającego mechanizmu zrobił coś, co w przypadku, gdy chcemy stworzyć coś ciut bardziej wyrafinowanego niż badanie, jaki login jest zalogowany na połączeniu, wymaga od użytkownika przysłowiowego "łapania się lewą ręką za prawe ucho"? Wie ktoś? :-)
I już zupełnie na marginesie – także w zeszłym tygodniu próbowałem zaprzęgać Policy-Based Management (PBM) do wykrywania jednego z typowych błędów deweloperskich w procedurach. I co? I guzik. Okazało się, że w PBM wzorzec procedury składowanej nie ma takiej właściwości, która pozwala zbadać zawartość definicji procedury… (i jakoś nie bardzo mnie pociesza, że w wyrażeniach w PBM mogę użyć dostępnej funkcji ExecuteSql i wydłubać zapytaniem z odpowiedniego widoku definicję obiektu…) Opadło mi wszystko, włącznie z chęcią używania PBM. To już sobie napiszę rękoma DDL trigger i przynajmniej będę dokładnie wiedział, co w kodzie piszczy.
Suma sumarum, za takie "ficzery" w SQL Serverze ja dziękuję. Nijak mi nie pomagają, a jedynie zjadają czas spędzony na bezowocnym R&D. Czas, który mógłbym spożytkować na pisanie swojego kodu, który robiłby to samo, tylko pewnie lepiej ;-)
[PL] SQL Server dla DBA – Zdań kilka o SQL Trace
Apr 12th
Pomyślałem, że napiszę krótki wpis o SQL Trace, czyli o mechanizmie, który powinien znaleźć się w arsenale każdego DBA pracującego z SQL Serverem.
Namierzyć Profilera
Zakładam, że każdy DBA prędzej czy później musi nauczyć się używać aplikacji SQL Server Profiler. Czasem jednak śledzenie przy użyciu Profilera nie jest mile widziane na serwerze produkcyjnym. Zwłaszcza, jeśli do tego dodać, że śledzenie może odbić się negatywnie na wydajność serwera. Niejednokrotnie zdarzało mi się, że przechwytując Profilerem za dużo zdarzeń (lub zbyt ciężkich zdarzeń – np. plany wykonania), obserwowałem “zamulanie” serwera.
Jak znaleźć trace’y otwarte z użyciem Profilera? Ano tak:
SELECT id, path, file_position, reader_spid FROM sys.traces WHERE is_rowset = 1;
Przykładowy wynik:
W zasadzie po dłuższych obserwacjach doszedłem do wniosku, że można też pytać o reader_spid o wartości innej niż NULL.
Kolumny, które wybrałem w powyższym zapytaniu są w moim odczuciu najbardziej przydatne:
- id – unikalny numer trace’a, dzięki niemu można dowiedzieć się więcej o danej sesji śledzenia (o tym za moment) lub po prostu zakończyć sesję śledzenia odpowiednimi wywołaniami procedury sp_trace_setstatus,
- path - jeśli zapisujemy do pliku .trc (najczęstszy scenariusz poza “zwykłym” śledzeniem za pomocą Profilera), ścieżkę do aktualnego pliku znajdziemy właśnie w tej kolumnie,
- file_position - rozmiar pliku, jeśli trace prowadzi zapis do takowego,
- reader_spid – jeśli Profiler czyta trace, tu znajdziemy identyfikator sesji otwartej przez aplikację (i można dzięki temu poleceniem KILL sprawnie zakończyć sesję Profilera).
Co właściwie śledzi trace?
Jednym z najbardziej niedocenianych źródeł informacji dla DBA jest default trace. Jest to systemowy trace uruchamiany domyślnie wraz ze startem instancji. W widoku katalogowym sys.traces zawsze ma id równe 1. Można go włączać / wyłączać zmieniając opcję ‘default trace’. Poniżej włączanie wspomnianej opcji:
EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'default trace enabled', 1; RECONFIGURE;
Co śledzi taki domyślny trace? Przekonajmy się:
SELECT DISTINCT e.trace_event_id, e.name FROM sys.traces AS t CROSS APPLY sys.fn_trace_geteventinfo(t.id) AS i INNER JOIN sys.trace_events AS e ON i.eventid = e.trace_event_id WHERE t.is_default = 1 ORDER BY e.trace_event_id;
Wynik:
trace_event_id name -------------- ------------------------------ 18 Audit Server Starts And Stops 20 Audit Login Failed 22 ErrorLog 46 Object:Created 47 Object:Deleted 55 Hash Warning 69 Sort Warnings 79 Missing Column Statistics 80 Missing Join Predicate 81 Server Memory Change 92 Data File Auto Grow 93 Log File Auto Grow 94 Data File Auto Shrink 95 Log File Auto Shrink 102 Audit Database Scope GDR Event 103 Audit Schema Object GDR Event 104 Audit Addlogin Event 105 Audit Login GDR Event 106 Audit Login Change Property Event 108 Audit Add Login to Server Role Event 109 Audit Add DB User Event 110 Audit Add Member to DB Role Event 111 Audit Add Role Event 115 Audit Backup/Restore Event 116 Audit DBCC Event 117 Audit Change Audit Event 152 Audit Change Database Owner 153 Audit Schema Object Take Ownership Event 155 FT:Crawl Started 156 FT:Crawl Stopped 157 FT:Crawl Aborted 164 Object:Altered 167 Database Mirroring State Change 175 Audit Server Alter Trace Event 218 Plan Guide Unsuccessful
Sporo, bo aż 35 rodzajów zdarzeń. Ale są to na ogół zdarzenia, które nie występują z dużą częstotliwością.
W podobny sposób, a używając jedynie innych funkcji i widoków katalogowych, można dowiedzieć się o tym, jakich kolumn dla każdego zdarzenia używa trace, oraz jakie są w nim ustawione filtry.
Default trace w użytku
Kilka przykładowych zastosowań default trace’a, niektóre podpatrzone w Management Studio ;-)
Zmiany w konfiguracji serwera
SELECT
e.StartTime,
e.SessionLoginName,
e.TextData
FROM sys.traces AS t
CROSS APPLY sys.fn_trace_gettable(
LEFT(t.path, LEN(t.path)-PATINDEX('%\%', REVERSE(t.path))) + '\log.trc',
DEFAULT
) AS e
WHERE t.is_default = 1
AND (
-- Error = 15457 - common sp_configure message
(e.EventClass = 22 AND e.Error = 15457)
OR
-- DBCC event
(e.EventClass = 116 AND e.TextData LIKE '%TRACEO%(%')
)
ORDER BY e.StartTime DESC;
Wynik:
Warte uwagi jest chyba tylko to, że pierwszy plik default trace’a ma nazwę log.trc. Ścieżkę da się odczytać z kolumny path widoku sys.traces. Reszta zapytania bez historii – wybrać dwa rodzaje zdarzeń (Errorlog oraz DBCC) dla trace’a, dla którego w kolumnie is_default w widoku sys.traces widnieje wartość 1.
DDL wykonywany na obiektach w bazach danych
SELECT
e.StartTime,
e.SessionLoginName,
e.DatabaseName,
e.ObjectName,
v.subclass_name AS ObjectType,
CASE e.EventClass
WHEN 46 THEN 'CREATE'
WHEN 47 THEN 'ALTER'
WHEN 164 THEN 'DROP'
END AS SchemaChange
FROM sys.traces AS t
CROSS APPLY sys.fn_trace_gettable(
LEFT(t.path, LEN(t.path)-PATINDEX('%\%', REVERSE(t.path))) + '\log.trc',
DEFAULT
) AS e
INNER JOIN sys.trace_subclass_values AS v
ON v.trace_event_id = e.EventClass
AND v.trace_column_id = 28
AND v.subclass_value = e.ObjectType
WHERE t.is_default = 1
AND e.EventClass IN (46, 47, 164)
AND e.EventSubClass = 0 -- eliminate junk
AND e.DatabaseID <> 2 -- not tempdb
AND e.ObjectType <> 21587 -- not stats
ORDER BY e.StartTime DESC;
Wynik:
Problemem może być odczytanie schematu obiektu, jeśli obiekt skasowano. Komentarz “eliminate junk” dodałem obok filtra, który pozwolił na odfiltrowanie wierszy mówiących o operacjach na statystykach.
Notka: dwa powyższe przykłady wziąłem ze standardowych raportów dostępnych w Management Studio pod prawym przyciskiem na folderze Databases w oknie Object Explorer.
Automatyczne zmiany rozmiaru plików
SELECT
e.StartTime,
CASE e.EventClass
WHEN 92 THEN 'Data File Auto Grow'
WHEN 93 THEN 'Log File Auto Grow'
WHEN 94 THEN 'Data File Auto Shrink'
WHEN 95 THEN 'Log File Auto Shrink'
END AS EventDesc,
e.DatabaseName,
e.FileName
FROM sys.traces AS t
CROSS APPLY sys.fn_trace_gettable(
LEFT(t.path, LEN(t.path)-PATINDEX('%\%', REVERSE(t.path))) + '\log.trc',
DEFAULT
) AS e
WHERE t.is_default = 1
AND e.EventClass IN (92, 93, 94, 95)
ORDER BY e.StartTime DESC;
Wynik:
Czarna skrzynka
SQL Server daje możliwość uruchomienia szybkiego w konfiguracji trace’a – tzw. BlackBox trace’a.
Kod do jego stworzenia i uruchomienia jest naprawdę krótki:
DECLARE @trace_id int; EXEC sp_trace_create @trace_id OUT, 8; EXEC sp_trace_setstatus @trace_id, 1;
Co śledzi taki trace?
SELECT DISTINCT e.trace_event_id, e.name FROM sys.traces AS t CROSS APPLY sys.fn_trace_geteventinfo(t.id) AS ei CROSS APPLY sys.fn_trace_getinfo(t.id) AS i INNER JOIN sys.trace_events AS e ON ei.eventid = e.trace_event_id WHERE i.property = 1 AND i.value = 8 ORDER BY e.trace_event_id;
Zapytanie zwraca raptem cztery rodzaje zdarzeń:
trace_event_id name -------------- ----------------- 11 RPC:Starting 13 SQL:BatchStarting 16 Attention 33 Exception
Do czego można użyć takiego śledzenia? Szybkie uruchomienie, dość zgrubne zdarzenia… Jeśli już, używałbym w sytuacji, gdy chciałbym dowiedzieć się, co stało się tuż przed “reprodukowalnym” problemem :-) Tak czy owak, jeśli ktoś kiedyś tego użył produkcyjnie, chętnie poczytam o innych zastosowaniach.
BlackBox trace’a znajdujemy w widoku sys.traces używając funkcji sys.fn_trace_getinfo (@options = 8). Swoje dane zapisuje on do dwóch plików o łącznym maksymalnym rozmiarze 10 MB (to mniej niż default trace, który zapisuje do 5 plików po maks. 20 MB każdy). Nowe zdarzenia w obu trace’ach – default i BlackBox – nadpisują stare zdarzenia, jeśli osiągnięty został maksymalny rozmiar ostatniego z dostępnych dla danego trace’a plików. Pliki obu trace’ów zapisywane są w folderze Log w ścieżce roboczej SQL Servera (a więc tam, gdzie ERRORLOG).
Podsumowanie
Sądzę, że warto poznać możliwości obu wymienionych trace’ów. Choćby po to, by pobawić się w wyciąganie metadanych trace’ów albo poznać lepiej procedury do ich obsługi. Nie polecam też wyłączania default trace’a, chyba, że polityka bezpieczeństwa firmy lub organizacji wyraźnie mówi o tym, że należy go wyłączyć.
Jeśli masz jakieś ciekawe przemyślenia na temat obu trace’ów albo znasz jakieś ciekawostki dotyczące trace’ów ogólnie, podziel się nimi (mogę je opublikować jako dodatek do tego wpisu).
[PL] Materiały z sesji o XML z 53. spotkania PLSSUG Warszawa
Apr 11th
W czwartek, 7 kwietnia, Paweł Skolimowski poprowadził prezentację pt. “Niechaj Cię nie onieśmiela gąszcz misterny XML-a” w ramach 53. spotkania Polskiej Grupy Użytkowników SQL Server (PLSSUG).
Prezentacja wypadła, moim zdaniem, znakomicie. Paweł przygotował profesjonalne slajdy (zmieniające się jak w kalejdoskopie kolorowanie składni w PPT – szacunek!) oraz obrazowe przykłady kodu T-SQL. Jak na pierwszą prezentację przed, co by nie mówić, sporym gronem publiczności (na sali było około 50-60 osób), dla mnie bomba.
A ponieważ na nowej witrynie PLSSUG, póki co, nie ma modułu do przechowywania i udostępniania materiałów z prezentacji (pracujemy nad tym!), uzgodniłem z Pawłem, że materiały z jego sesji zamieszczę w ramach mojego bloga, by zainteresowane osoby mogły pobrać kod i slajdy.
Krótki komentarz Pawła do materiałów:
Prezentacja jest tak skonstruowana, że w okolicach lewego dolnego rogu slajdu pojawia się nazwa skryptu, który jest przykładem omawianego na obecnym slajdzie (oraz kilku wcześniejszych) materiału. W większości skryptów używane są pliki XML, dlatego wymagana jest zmiana używanej ścieżki (f:\#Transfer\…) na inną. Musi ona prowadzić do folderu, do którego ma dostęp serwer MSSQL.
Polecam!
Pobierz materiały z prezentacji “Niechaj Cię nie onieśmiela gąszcz misterny XML-a” (ZIP, 1 MB)
[PL] SQL Server dla DBA – sys.dm_os_performance_counters
Apr 4th
W komentarzach do pierwszego wpisu z serii SQL Server dla DBA Robert Kubalski przypomniał o istnieniu widoku sys.dm_os_performance_counters, który może być źródłem cennych informacji o instancji SQL Server i bazach danych istniejących w ramach owej instancji. Pomyślałem, że warto by było napisać, w jaki sposób DBA może interpretować wartości zwracane w wyniku zapytania do wspomnianego widoku.
Pierwsze podejście – typy liczników
Widok dynamiczny sys.dm_os_performance_counters umożliwia podejrzenie wartości liczników monitora wydajności (perfmon.exe) dedykowanych konkretnej instancji SQL Server (tej, z której zadajemy zapytanie do widoku). Jednak nie wszystkie wartości liczbowe można bezpośrednio interpretować. Aby dowiedzieć, w jaki sposób należy interpretować wartości zwracane dla poszczególnych liczników, trzeba poznać typy liczników.
Istnieje kilka typów liczników (są to typy liczników WMI), a listę ich kodów można uzyskać w prosty sposób:
SELECT DISTINCT cntr_type FROM sys.dm_os_performance_counters ORDER BY cntr_type;
Oto owe typy liczników i ich interpretacja:
- 65792 - PERF_COUNTER_LARGE_RAWCOUNT – obrazuje ostatnio przechwyconą wartość licznika; tego typu liczniki interpretujemy bezpośrednio – odczytana wartość może być od razu interpretowana (bez przeliczania)
- 272696576 - PERF_COUNTER_BULK_COUNT – oznacza licznik, którego wartości są liczone per zadany interwał czasowy; do wyliczenia sensownej wartości takiego potrzebne są dwa pomiary (a następnie różnicę między pomiarami należy podzielić przez ilość jednostek czasu, np. ilość sekund)
- 537003264 – PERF_LARGE_RAW_FRACTION – taki licznik używany jest do obliczenia ilorazu reprezentującego wskaźnik procentowy (jest podstawiany w liczniku dzielenia, a więc do pełni szczęścia potrzebujemy mianownika – patrz typ PERF_LARGE_RAW_BASE poniżej); niektóre liczniki (np. Buffer Cache Hit Ratio) są reprezentowane jako wartości procentowe i powstają jako wynik dzielenia licznika typu 537003264 przez licznik typu 1073939712
- 1073874176 - PERF_AVERAGE_BULK – ten licznik jest używany do obliczenia ilorazu reprezentującego średnią wartość(podstawiany w liczniku dzielenia – znów potrzebny jest mianownik w postaci licznika typu PERF_LARGE_RAW_BASE); niektóre liczniki (np. Average Wait Time (ms)) reprezentują wartości średnie i powstają jako wynik dzielenia wartości licznika typu 1073874176 przez wartości odpowiedniego licznika typu 1073939712
- 1073939712 - PERF_LARGE_RAW_BASE – licznik używany do obliczenia ilorazu (podstawiany w mianowniku dzielenia); stanowi parę z licznikiem typu 537003264 lub 1073874176.
A zatem, jeśli zobaczysz takie dwa liczniki:
znając typy liczników możesz wywnioskować, że należy podzielić wartość z kolumny cntr_value dla licznika Buffer cache hit ratio przez wartość z tej samej kolumny dla licznika Buffer cache hit ratio base (w tym konkretnym przypadku otrzymujemy wartość 1, co oznacza, że instancja notuje idealnie 100% trafień w bufor danych).
Drugie podejście – licznik, obiekt, instancja
Liczniki są podzielone pomiędzy obiekty. Obiekt można traktować jak kategorię. Oczywiście, są to te same obiekty, które instancja SQL Server dodaje do listy obiektów do użycia w monitorze wydajności. I jednocześnie trzeba pamiętać, że widok sys.dm_os_performance_counters zwraca wyłącznie wartości tych liczników, które pochodzą od danej instancji, na której wykonujemy zapytanie. Dla niektórych liczników, tak jak w perfmon.exe istnieje wiele instancji. Przykładem mogą być liczniki mówiące o rozmiarze dzienników transakcji w bazach danych oraz ilości miejsca zajętego w każdym z dzienników transakcji:
SELECT * FROM sys.dm_os_performance_counters WHERE counter_name LIKE '%Log File%';
Trzecie podejście – przykładowe zapytania i liczniki
O ile zapytanie o wszystkie liczniki nie obciąża praktycznie serwera (liczba odczytów i zużycie procesora są niemal zerowe, a sam widok zwraca co najwyżej kilka tysięcy wierszy – liczba wierszy zależy od ilości baz danych na instancji), o tyle zazwyczaj interesuje nas konkretny licznik lub grupa liczników.
Page Life Expectancy
Page Life Expectancy z obiektu Buffer Manager czyli czas życia stron w pamięci mierzony w sekundach. Ten licznik przydaje się, by zweryfikować, czy przypadkiem strony danych z pamięci nie są zrzucane do pliku stronicowania. Gwałtowny spadek tego licznika zazwyczaj mówi o opróżnieniu bufora danych – czy to celowym, czy wymuszonym przez system (prosty sposób na stwierdzenie braku ustawionego uprawnienia Lock pages in memory).
SELECT object_name, counter_name, cntr_value FROM sys.dm_os_performance_counters WHERE [object_name] LIKE '%Buffer Manager%' AND counter_name = 'Page Life Expectancy';
Przykładowy wynik (niski, bo zmierzony tuż po restarcie usługi):
Przy okazji warto zauważyć, że kolumny tekstowe widoku sys,dm_os_performance_counters są typu nchar(128), a więc nazwy obiektów, liczników oraz instancji są dopełniane do 128 znaków spacjami. Jest to istotna sprawa przy pisaniu zapytań, zwłaszcza z użyciem operatora LIKE (warto dokładać znak % na końcu wzorca, by nie nadziać się na pułapkę związaną z dodatkowymi spacjami w nazwach).
Buffer cache hit ratio
Buffer cache hit ratio z obiektu Buffer Manager czyli procent trafień zapytań w strony znajdujące się w buforze danych. Im bliżej wartości 1 (oznaczającej 100%), tym lepiej.
SELECT
c.object_name,
c.counter_name,
CASE
WHEN b.cntr_value = 0 THEN 0
ELSE CONVERT(numeric(38,2), c.cntr_value * 1.0 / b.cntr_value)
END AS value
FROM sys.dm_os_performance_counters AS c
INNER JOIN sys.dm_os_performance_counters AS b
ON c.object_name = b.object_name
AND b.counter_name LIKE RTRIM(c.counter_name) + '%'
AND c.instance_name = b.instance_name
AND b.cntr_type = 1073939712
WHERE c.[object_name] LIKE '%Buffer Manager%'
AND c.counter_name = 'Buffer cache hit ratio';
Przykładowy wynik:
Powyższą technikę odszukiwania licznika i mianownika do obliczania ilorazu można stosować do większości liczników, których wartości wyliczamy z ilorazu. Są jednak przypadki, kiedy nazwa licznika i nazwa współczynnika używanego w mianowniku ilorazu różnią się nie tylko słowem “Base”. Przykładem może być licznik Avg. Time Between Batches (ms) z obiektu Broker TO Statistics:
Połączenia i aktywne transakcje
Liczniki Active Transactions z obiektu Databases oraz User Connections z obiektu General statistics pozwalają zorientować się, jaka jest aktualna liczba połączeń do instancji SQL Server oraz ile aktywnych transakcji jest otwartych w kontekście każdej bazy danych.
SELECT [object_name], counter_name, instance_name, cntr_value FROM sys.dm_os_performance_counters WHERE counter_name IN ( 'Active Transactions', 'User Connections' );
Przykładowy wynik:
Podsumowanie
Widok sys.dm_os_performance_counters daje DBA możliwość śledzenie pokaźnej ilości liczników mówiących wiele o kondycji instancji SQL Server. Co ważne, administrator nie musi korzystać z żadnych zewnętrznych narzędzi. Może wykorzystać widok do zbierania danych wydajnościowych do celów analizy, budowania tzw. baseline, klasycznego monitorowania, itd.
Widok ten jest szeroko wykorzystywany przez różne narzędzia firm trzecich (np. Quest Spotlight), jak również przez narzędzia dostępne w SQL Server (np. Performance Studio). Warto się nim zainteresować, zwłaszcza, jeśli naszym celem jest zbudowanie własnego rozwiązania do monitorowania serwera (patrz także wpis pt. SQL Server – Interaktywny performance dashboard dla ubogich).
[PL] Materiały z prezentacji "Struktury danych w SQL Server 2008 R2"
Mar 4th
W czwartek, 3 marca, miałem przyjemność poprowadzić prezentację pt. "Struktury danych w SQL Server 2008 R2" w ramach 52. spotkania Polskiej Grupy Użytkowników SQL Server (PLSSUG).
Dziękuję tym, którzy przybyli na czwartkowe spotkanie (liczne grono!). Przyznam, że miałem obawy, czy taka "geekowska" sesja spełni oczekiwania wymagającej publiczności, ale z tego, co mówili mi następnego dnia niektórzy uczestnicy spotkania, nie było tragicznie :-) Przyznaję, że przygotowując się do prezentacji, wiele się nauczyłem (choćby o tym, jak SQL Server rozmieszcza dane w plikach czy o tym, jak działa ghost cleanup).
Tradycyjnie już publikuję na stronie zasobów materiały (skrypty i slajdy) z mojej sesji. Mam nadzieję, że skrypty okażą się przydatne w zdobywaniu dogłębnej wiedzy na temat tego, jak działa SQL Server, jak przechowuje dane i na nich operuje.
Przy okazji też już dzisiaj zapraszam na 53. spotkanie PLSSUG Warszawa, które odbędzie się najpewniej 7 kwietnia. Z tego, co wiem, na spotkaniu będzie sporo o XML-u w SQL Server (autorskie zastosowania?). Prawdopodobnie pojawi się też temat ze świata .NET, ponieważ wygląda na to, że wraz z nami – "ludźmi od SQL-a" – spotkają się "ludzie od .NET" czyli Warszawska Grupa .NET (tak, tak – właśnie ta – jedna z najstarszych grup pasjonackich w Polsce, która już kiedyś w swej historii organizowała wspólne spotkania z PLSSUG)!!! Serdecznie zapraszam.
Pobierz materiały z prezentacji “Struktury danych w SQL Server 2008 R2” (ZIP, 281 KB)
[PL] Time to be a Master (?)
Feb 19th
Niektórzy trafnie zauważyli, że ostatni wpis na moim blogu pojawił się dość dawno. To prawda. Niestety, czasem człowiek ma na głowie tyle zajęć, że blogowanie schodzi na plan dalszy. Ostatnie tygodnie były dla mnie czasem wytężonej pracy. A przede mną kolejne pracowite dni. Dlaczego? Między innymi dlatego, że przygotowuję się do egzaminu 88-970: SQL Server 2008 Microsoft Certified Master: Knowledge Exam.
Microsoft Certified Master (MCM) jest ponoć tytułem dla "twardzieli" i ekspertów ;-) No cóż, czy czuję się ekspertem? Z pewnością są takie obszary SQL Servera, na których znam się całkiem nieźle. Są też i takie, których najchętniej w ogóle bym w życiu nie dotykał :-)
W samym programie SQL Server MCM zaszły ostatnio spore zmiany. Nadal wymagane są dwa certyfikaty: MCITP:DBA i MCITP:Dev. Nadal trzeba zapłacić sporo za egzaminy (egzamin oraz lab – w sumie 2500 dolarów). Ale nie jest już wymagane odbycie kosztownego (18 000 dolarów) i wyczerpującego szkolenia w Redmond.
Do egzaminu, podobno bardzo trudnego, trzeba zapewne umieć więcej niż by się chciało, więc w wolnym czasie uzupełniam braki w mojej wiedzy n/t SQL Servera. Między innymi oglądam nagrania z serii SQL Server 2008 Microsoft Certified Master Readiness Videos. Na całe szczęście dobrzy ludzie napisali porządne skrypty PowerShell, dzięki którym można pobrać wszystkie filmy ze wspomnianej serii :-)
Recepty na zostanie "masterem" raczej nie ma, choć na blogu MCM podano kilka pomocnych uwag. Receptę na pewno zna Maciek Pilecki, który od kilku dni jest pierwszym znanym mi SQL Server MCM w Polsce. Gratulacje dla Maćka. Teraz trzeba spróbować swoich sił :-) Jeśli ktoś zamierza przystępować do tej certyfikacji, proszę o kontakt. Byłoby fajnie wymienić się doświadczeniami, a być może trochę razem poedukować.
Czy mi się uda zostać MCM, czy nie, na pewno to, czego się nauczę podczas przygotowań do egzaminu, będzie cennym doświadczeniem. Egzamin już w przyszłym miesiącu, więc siadam do nauki, a jak nauczę się czegoś ciekawego, nie omieszkam opisać tego na blogu. Nawet mam już parę pomysłów, więc wkrótce powinien pojawić się jakiś nowy wpis. Trzymajcie za mnie kciuki. Time to be a Master? Czemu nie!
[PL] SQL Server dla DBA – zapomniany widok sys.master_files
Jan 24th
Pomyślałem ostatnio, że fajnie by było, jakbym częściej umieszczał nieco prostsze, acz użyteczne kawałki kodu T-SQL, głównie dedykowane administratorom baz danych. Na dobry początek wykorzystanie często zapominanego i niedocenianego widoku systemowego sys.master_files istniejącego w SQL Server 2005 i nowszych wersjach.
Widok ten jest znakomitym źródłem informacji na temat plików baz danych. Można się dzięki niemu szybko zorientować w takich tematach, jak:
- czy pliki bazy leżą na tym samym dysku logicznym,
- jaki jest rozmiar oraz parametry automatycznego powiększania każdego z plików,
- ile plików jakiego typu wchodzi w skład każdej z baz danych,
- jaki jest status każdego pliku (OFFLINE / ONLINE, READ_ONLY, etc.),
- jaki był ostatni log sequence number (LSN) backupu wykonanego na pliku.
Z mojej perspektywy najbardziej przydatna informacja to (oczywiście?) lokalizacja i rozmiar plików (kolumny physical_name oraz size). Rozmiar w kolumnie size liczony jest w 8-kilobajtowych stronach, więc po drodze trzeba wykonać kilka prostych obliczeń, by uzyskać rozmiar pliku na przykład wyrażony w megabajtach. Poniższe zapytanie jest przykładem, jak z widoku sys.master_files można odczytać łączny rozmiar plików każdego rodzaju (log, dane, etc.) dla każdej z baz danych.
SELECT DB_NAME(database_id) AS database_name, CONVERT( numeric(38,2), SUM( CASE type WHEN 0 THEN size * 8192.0 / (1024 * 1024) ELSE 0 END ) ) AS data_size_mb, CONVERT( numeric(38,2), SUM( CASE type WHEN 1 THEN size * 8192.0 / (1024 * 1024) ELSE 0 END ) ) AS log_size_mb, CONVERT( numeric(38,2), SUM( CASE type WHEN 2 THEN size * 8192.0 / (1024 * 1024) ELSE 0 END ) ) AS filestream_size_mb, CONVERT( numeric(38,2), SUM( CASE type WHEN 4 THEN size * 8192.0 / (1024 * 1024) ELSE 0 END ) ) AS fulltext_size_mb FROM sys.master_files GROUP BY database_id ORDER BY DB_NAME(database_id);
Powyższy skrypt umożliwia natychmiastową odpowiedź na pytania w stylu: “która baza jest największa?”, “która baza używa filestream?”, “gdzie log jest większy niż dane?”.
[EDYCJA 2011-01-26]: Czytelnik o nicku wacio słusznie zwrócił mi uwagę, że dla kontenerów FILESTREAM w widoku sys.master_files kolumna size zawsze przyjmuje wartość 0. Dzięki za czujność. Dla mnie to oczywisty bug, ale na szczęście oczywiste obejście tego problemu jest podane w BOL dla Denali: This field is populated as zero for FILESTREAM containers. Query the sys.database_files catalog view for the actual size of FILESTREAM containers. Mimo to, nadal uważam, że sys.master_files jest ciekawym i przydatnym widokiem :-)

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




