SQL Server

[PL] SQL Server – Jak optymalizator robi ze mnie głupca

VN:F [1.7.9_1023]
Rating: 5.0/5 (2 votes cast)

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:

image

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:

image

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”

VN:F [1.7.9_1023]
Rating: 0.0/5 (0 votes cast)

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?

VN:F [1.7.9_1023]
Rating: 5.0/5 (1 vote cast)

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

VN:F [1.7.9_1023]
Rating: 0.0/5 (0 votes cast)

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

VN:F [1.7.9_1023]
Rating: 0.0/5 (0 votes cast)

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:

image

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:

image

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:

image

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:

image

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

VN:F [1.7.9_1023]
Rating: 0.0/5 (0 votes cast)

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

VN:F [1.7.9_1023]
Rating: 5.0/5 (2 votes cast)

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)
  • 537003264PERF_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:

image

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%';

image

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):

image

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:

image

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:

image

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:

image

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"

VN:F [1.7.9_1023]
Rating: 0.0/5 (0 votes cast)

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 (?)

VN:F [1.7.9_1023]
Rating: 4.0/5 (2 votes cast)

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.

mcm

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

VN:F [1.7.9_1023]
Rating: 0.0/5 (0 votes cast)

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 :-)