Jak wie każdy doświadczony administrator produkcyjny, często znajdujesz się pod znaczną presją, aby jak najszybciej diagnozować i łagodzić problemy z wydajnością bazy danych. Oto trzy rzeczy, z których możesz skorzystać, w zależności od obciążenia i infrastruktury, aby mieć bardzo zauważalny pozytywny wpływ na wydajność bazy danych.
Podstawowe dostrajanie indeksu sklepu wierszy
Większość instancji SQL Server, z którymi się zetknąłem w swojej karierze, miała stosunkowo łatwe możliwości dostrajania indeksu magazynu wierszy. Jedną z zalet dostrajania indeksu sklepu wierszy jest to, że jest on częściej pod Twoją bezpośrednią kontrolą jako administrator baz danych, zwłaszcza w porównaniu z dostrajaniem zapytań lub procedur składowanych, które często znajdują się pod kontrolą programistów lub dostawców zewnętrznych.
Niektórzy administratorzy baz danych niechętnie dostrajają indeksy (zwłaszcza w bazach danych innych firm), ponieważ obawiają się, że coś zepsuje lub narazi na szwank wsparcie dostawcy dla bazy danych lub aplikacji. Oczywiście musisz być bardziej ostrożny z bazami danych innych firm i spróbować skontaktować się z dostawcą przed samodzielnym wprowadzeniem jakichkolwiek zmian w indeksie, ale w niektórych sytuacjach możesz nie mieć innej realnej alternatywy (oprócz rzucania szybszego sprzętu i pamięci masowej w przypadku problemu ).
Możesz uruchomić kilka kluczowych zapytań z moich zapytań dotyczących informacji diagnostycznych programu SQL Server, aby uzyskać dobry pomysł, jeśli masz kilka możliwości łatwego dostrajania indeksu w swojej instancji lub bazie danych. Należy zwracać uwagę na brakujące żądania indeksowania, ostrzeżenia o brakującym indeksie, niewykorzystane lub nieużywane indeksy nieklastrowane oraz możliwe możliwości kompresji danych.
Prawidłowe dostrojenie indeksu wymaga pewnego doświadczenia, dobrego osądu i wiedzy o obciążeniu pracą. Zbyt często zdarza się, że ludzie wykonują nieprawidłowe dostrajanie indeksów, pochopnie dokonując wielu zmian w indeksie bez przeprowadzania właściwej analizy.
Oto kilka zapytań, których lubię używać na poziomie bazy danych:
-- Missing Indexes for current database by Index Advantage (Query 1) (Missing Indexes) SELECT DISTINCT CONVERT(decimal(18,2), user_seeks * avg_total_user_cost * (avg_user_impact * 0.01)) AS [index_advantage], migs.last_user_seek, mid.[statement] AS [Database.Schema.Table], mid.equality_columns, mid.inequality_columns, mid.included_columns, migs.unique_compiles, migs.user_seeks, migs.avg_total_user_cost, migs.avg_user_impact, OBJECT_NAME(mid.[object_id]) AS [Table Name], p.rows AS [Table Rows] FROM sys.dm_db_missing_index_group_stats AS migs WITH (NOLOCK) INNER JOIN sys.dm_db_missing_index_groups AS mig WITH (NOLOCK) ON migs.group_handle = mig.index_group_handle INNER JOIN sys.dm_db_missing_index_details AS mid WITH (NOLOCK) ON mig.index_handle = mid.index_handle INNER JOIN sys.partitions AS p WITH (NOLOCK) ON p.[object_id] = mid.[object_id] WHERE mid.database_id = DB_ID() AND p.index_id < 2 ORDER BY index_advantage DESC OPTION (RECOMPILE); ------ -- Look at index advantage, last user seek time, number of user seeks to help determine source and importance -- SQL Server is overly eager to add included columns, so beware -- Do not just blindly add indexes that show up from this query!!! -- Find missing index warnings for cached plans in the current database (Query 2) (Missing Index Warnings) -- Note: This query could take some time on a busy instance SELECT TOP(25) OBJECT_NAME(objectid) AS [ObjectName], cp.objtype, cp.usecounts, cp.size_in_bytes, query_plan FROM sys.dm_exec_cached_plans AS cp WITH (NOLOCK) CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp WHERE CAST(query_plan AS NVARCHAR(MAX)) LIKE N'%MissingIndex%' AND dbid = DB_ID() ORDER BY cp.usecounts DESC OPTION (RECOMPILE); ------ -- Helps you connect missing indexes to specific stored procedures or queries -- This can help you decide whether to add them or not -- Possible Bad NC Indexes (writes >= reads) (Query 3) (Bad NC Indexes) SELECT OBJECT_NAME(s.[object_id]) AS [Table Name], i.name AS [Index Name], i.index_id, i.is_disabled, i.is_hypothetical, i.has_filter, i.fill_factor, s.user_updates AS [Total Writes], s.user_seeks + s.user_scans + s.user_lookups AS [Total Reads], s.user_updates - (s.user_seeks + s.user_scans + s.user_lookups) AS [Difference] FROM sys.dm_db_index_usage_stats AS s WITH (NOLOCK) INNER JOIN sys.indexes AS i WITH (NOLOCK) ON s.[object_id] = i.[object_id] AND i.index_id = s.index_id WHERE OBJECTPROPERTY(s.[object_id],'IsUserTable') = 1 AND s.database_id = DB_ID() AND s.user_updates > (s.user_seeks + s.user_scans + s.user_lookups) AND i.index_id > 1 AND i.[type_desc] = N'NONCLUSTERED' AND i.is_primary_key = 0 AND i.is_unique_constraint = 0 AND i.is_unique = 0 ORDER BY [Difference] DESC, [Total Writes] DESC, [Total Reads] ASC OPTION (RECOMPILE); ------ -- Look for indexes with high numbers of writes and zero or very low numbers of reads -- Consider your complete workload, and how long your instance has been running -- Investigate further before dropping an index! -- Breaks down buffers used by current database by object (table, index) in the buffer cache (Query 4) (Buffer Usage) -- Note: This query could take some time on a busy instance SELECT OBJECT_NAME(p.[object_id]) AS [Object Name], p.index_id, CAST(COUNT(*)/128.0 AS DECIMAL(10, 2)) AS [Buffer size(MB)], COUNT(*) AS [BufferCount], p.[Rows] AS [Row Count], p.data_compression_desc AS [Compression Type] FROM sys.allocation_units AS a WITH (NOLOCK) INNER JOIN sys.dm_os_buffer_descriptors AS b WITH (NOLOCK) ON a.allocation_unit_id = b.allocation_unit_id INNER JOIN sys.partitions AS p WITH (NOLOCK) ON a.container_id = p.hobt_id WHERE b.database_id = CONVERT(int, DB_ID()) AND p.[object_id] > 100 AND OBJECT_NAME(p.[object_id]) NOT LIKE N'plan_%' AND OBJECT_NAME(p.[object_id]) NOT LIKE N'sys%' AND OBJECT_NAME(p.[object_id]) NOT LIKE N'xml_index_nodes%' GROUP BY p.[object_id], p.index_id, p.data_compression_desc, p.[Rows] ORDER BY [BufferCount] DESC OPTION (RECOMPILE); ------ -- Tells you what tables and indexes are using the most memory in the buffer cache -- It can help identify possible candidates for data compression
Korzystanie z opóźnionej trwałości
Funkcja opóźnionej trwałości została dodana do produktu w SQL Server 2014, więc jest dostępna od dłuższego czasu. Opóźnione trwałe zatwierdzenia transakcji są asynchroniczne i zgłaszają zatwierdzenie transakcji jako pomyślne przed rekordy dziennika transakcji są faktycznie zapisywane w podsystemie pamięci masowej. Opóźnione trwałe transakcje nie stają się w rzeczywistości trwałe, dopóki wpisy dziennika transakcji nie zostaną usunięte na dysk.
Ta funkcja jest dostępna we wszystkich wersjach programu SQL Server. Mimo to rzadko widzę, jak jest używany, gdy patrzę na bazy danych klientów. Opóźniona trwałość otwiera możliwość utraty części danych, aż do całego bufora dziennika w najgorszym scenariuszu (jak wyjaśnił tutaj Paul Randal), więc zdecydowanie nie jest to odpowiednie dla scenariusza RPO, w którym absolutnie żadna utrata danych nie jest akceptowalna.
Opóźniona trwałość zmniejsza opóźnienia transakcji, ponieważ nie czeka na zakończenie operacji we/wy dziennika i zwrócenie kontroli z powrotem do klienta, a także zmniejsza blokowanie i rywalizację o dyski dla współbieżnych transakcji. Te dwie korzyści mogą często mieć bardzo pozytywny wpływ na wydajność zapytań i aplikacji przy odpowiednim obciążeniu dużym obciążeniem podczas zapisu.
Opóźniona trwałość najczęściej pomaga w ciężkich obciążeniach typu OLTP, które mają bardzo częste, małe transakcje zapisu, w których obserwuje się wysokie opóźnienie zapisu na poziomie plików z sys.dm_io_virtual_file_stats w pliku dziennika transakcji i/lub wysokie oczekiwanie na zapis WRITELOG ze strony sys. dm_os_wait_stats.
Możesz łatwo zmusić SQL Server 2014 lub nowszy do używania opóźnionej trwałości dla wszystkich transakcji (bez zmian w kodzie), uruchamiając następujące polecenie:
ALTER DATABASE AdventureWorks2014 SET DELAYED_DURABILITY = FORCED;
Miałem klientów, którzy programowo włączali i wyłączali opóźnioną trwałość o różnych porach dnia (np. podczas zaplanowanego ETL lub czynności konserwacyjnych). Miałem również klientów, którzy zawsze używają opóźnionej trwałości, ponieważ mają odpowiednie obciążenie pracą i tolerancję na ryzyko utraty danych.
Wreszcie miałem klientów, którzy nigdy nie rozważaliby używania opóźnionej trwałości lub po prostu nie potrzebują jej ze swoim obciążeniem pracą. Jeśli podejrzewasz, że Twoje obciążenie może skorzystaj z opóźnionej trwałości, ale obawiasz się możliwej utraty danych, to istnieją inne alternatywy, które możesz rozważyć.
Jedną z alternatyw jest funkcja trwałego buforu dziennika w dodatku SP1 dla programu SQL Server 2016, w której można utworzyć drugi, 20 MB plik dziennika transakcji na woluminie magazynu w trybie bezpośredniego dostępu (DAX), który jest hostowany na urządzeniu pamięci trwałej NV-DIMM. Ten dodatkowy plik dziennika transakcji jest używany do buforowania ogona dziennika, z dostępem na poziomie bajtów, który omija konwencjonalny stos pamięci na poziomie bloku.
Jeśli uważasz, że Twoje obciążenie może skorzystać na korzystaniu z funkcji utrwalonego bufora dziennika, możesz poeksperymentować z tymczasowym użyciem opóźnionej trwałości, aby sprawdzić, czy obciążenie rzeczywiście przynosi korzyści w zakresie wydajności, zanim wydasz pieniądze na pamięć trwałą NV-DIMM, którą musiałby użyć funkcji trwałego bufora dziennika.
Przenoszenie tempdb do pamięci masowej Intel Optane DC P4800X
Odniosłem wielki sukces z kilkoma niedawnymi klientami, którzy przenieśli swoje pliki bazy danych tempdb z innego rodzaju pamięci masowej na dysk logiczny, który był obsługiwany przez kilka kart pamięci Intel Optane DC P4800X PCIe NVMe (w programowej macierzy RAID 1).
Te karty pamięci są dostępne w pojemnościach 375 GB, 750 GB i 1,5 TB (chociaż pojemność 1,5 TB jest zupełnie nowa i wciąż trudna do znalezienia). Charakteryzują się wyjątkowo niskim opóźnieniem (znacznie niższym niż jakikolwiek rodzaj pamięci flash NAND), doskonałą wydajnością losowych operacji we/wy przy małej głębokości kolejki (znacznie lepszym niż pamięć flash NAND), ze stałymi czasami odpowiedzi odczytu przy bardzo dużym obciążeniu zapisu.
Charakteryzują się także wyższą wytrzymałością na zapis niż „intensywne” pamięci flash NAND dla przedsiębiorstw, a ich wydajność nie pogarsza się, ponieważ są bliskie pełnego zapełnienia. Te cechy sprawiają, że te karty bardzo dobrze nadają się do wielu ciężkich obciążeń tempdb, szczególnie dużych obciążeń OLTP i sytuacji, w których używasz RCSI w swoich bazach danych użytkowników (co umieszcza powstałe obciążenie magazynu wersji na tempdb).
Bardzo często obserwuje się również duże opóźnienia zapisu na poziomie plików w plikach danych tempdb z DMV sys.dm_io_virtual_file_stats, więc przeniesienie plików danych tempdb do magazynu Optane jest jednym ze sposobów bezpośredniego rozwiązania tego problemu, co może być szybsze i łatwiejsze niż w przypadku konwencjonalnego dostrajanie obciążenia.
Innym możliwym zastosowaniem kart pamięci Optane jest miejsce na pliki dziennika transakcji. Możesz również używać magazynu Optane ze starszymi wersjami SQL Server (o ile obsługuje je Twój system operacyjny i sprzęt). Jest to możliwa alternatywa dla używania opóźnionej trwałości (która wymaga SQL Server 2014) lub używania funkcji trwałego bufora dziennika (która wymaga SQL Server 2016 SP1).
Wniosek
Omówiłem trzy techniki oceniania szybkiego zwiększenia wydajności w SQL Server:
- Konwencjonalne strojenie indeksu magazynu wierszy ma zastosowanie do wszystkich wersji programu SQL Server i jest jednym z najlepszych narzędzi w Twoim arsenale.
- Opóźniona trwałość jest dostępna w SQL Server 2014 i nowszych i może być bardzo korzystna w przypadku niektórych typów obciążeń (i wymagań RPO). Utrwalony bufor dziennika jest dostępny w SQL Server 2016 SP1 i zapewnia podobne korzyści jak opóźniona trwałość, bez niebezpieczeństwa utraty danych.
- Przeniesienie niektórych typów plików baz danych do pamięci masowej Intel Optane może pomóc złagodzić problemy z wydajnością z tempdb lub plikami dziennika transakcji bazy danych użytkownika. Możesz używać pamięci Optane ze starszymi wersjami SQL Server i nie są wymagane żadne zmiany w kodzie ani konfiguracji.