Zmiana compatibility level w SQL Server

Zmiana compatibility level w SQL Server – wskazówki

Ostatnimi czasy wspierałem administratora baz danych podczas zmiany “compatibility level” dla jednej z baz hurtowni danych. Konkretnie zmiana dotyczyła przejścia z trybu 110 (zgodny z SQL Server 2012) na tryb 120 (zgodny z SQL Server 2014). Baza działała w trybie zgodnym z SQL Server 2012, natomiast Server został już wcześniej “upgradowany” do SQL Server 2014. W tym poście chciałbym podzielić się z problemami oraz wskazówkami dotyczącymi tej zmiany. Przedstawię swój plan pracy oraz poszczególne etapy oraz problemy, które napotkałem. Wskazówki mogą się okazać szczególnie przydatne w przypadku baz hurtowni danych, które uczestniczą w procesie ETL.

Teoria

Każda wersja SQL Server przynosi wiele zmian oraz nowości. Jako, że silnik bazy danych również ulega zmianie, systemy, które mają zostać migrowane do nowej edycji, mogą zacząć działać inaczej niż dotychczas. Oczywiści z założenia nowsza wersja powinna być lepsza i szybsza, natomiast może się również okazać, że po zmianie niektóre zapytania będą działać znacznie wolniej. “Compatibility level”, czyli tryb zgodności pozwala na zminimalizowanie takich ryzyk i zapewnia wsteczną kompatybilność. Warto zaznaczyć, że tryb zgodności jest ustawiany na poziomie pojedynczej bazy danych.

Opis środowisko

Zgodnie z tym co wspomniano powyej moja sytuacja wygldała następujaco:

  • Baza danych w charakterze hurtowni danych
  • Baza danych wykorzystywana w procesie ETL obsługiwanym przez SSIS
  • Bardzo duża ilość procedur składowanych w tym dużo długich i skomplikowanych
  • Wiele dużych tabel, większość z liczbą wierszy równą kilkuset milionów wierszy
  • Zainstalowany wcześniej SQL Server 2014 i ustawiony tryb zgodności z SQL Server 2012 (120)
  • Parametry serwera dobre, nie będę podawał szczegółowej specyfikacji
  • Proces ETL działał stabilnie i czas ładowania nie ulegał dużym wahaniom, mniej niż 15 minut różnicy pomiędzy najwolniejszym i najszybszym uruchomieniem w ciągu ostatnich kilku miesięcy
  • Plan zakładał przełączenie bazy w tryb zgodności zgodny z SQL Server 2014

Plan oraz szczegóły pracy

Przygotowanie do zmiany

  1. Sprawdzenie szczegółów technicznych oraz zmian pomiędzy wersjami – na samym początku powinniśmy sprawdzić szczegóły techniczne oraz zapoznać się ze zmianami pomiędzy poszczególnymi wersjami. Informacje te dostarcza Microsoft na stronach MSDN: https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-database-transact-sql-compatibility-level oraz https://msdn.microsoft.com/en-us/library/dn673537.aspx Po tej lekturze, znając własną bazę oraz znając charakter naszych danych powinniśmy wiedzieć czego się spodziewać.
  2. Sprawdzenie potencjalnych błędów – przeglądniecie Internetu w poszukiwaniu typowych problemów. Oczywiście nie będziemy pierwszymi developerami, którzy dokonują migracji lub przełączają tryb zgodności, więc dobrym pomysłem jest zapoznać się z potencjalnymi problemami, które zostały już opisane.
  3. Sprawdzenie kompatybilności kodu – wykorzystanie Microsoft Data Migration Assistant. Microsoft przygotował narzędzie, które pozwoli nam sprawdzić kod bazy danych oraz jego zgodność pomiędzy wersjami SQL Servera. Narzędzie dostępne jest tutaj: https://www.microsoft.com/en-us/download/details.aspx?id=53595

Jako wynik działania aplikacji uzyskamy listę potencjalnych błędów oraz wskazówek jak je rozwiaząć. W moim przypadku zostałem poinformowany o następujących przypadkach:

  • użycie starego typu złączeń w kilku zapytaniach (“Unqualified Join(s)”)
  • użycie typów danych, które wkrótce mogą przestać być wspierane, np. TEXT (“Deprecated data types TEXT, IMAGE or NTEXT”)
  • użycie ORDER BY w niewłaściwy sposób (“ORDER BY specifies integer ordinal”)
  • odwołania do nieudokumentowanych widoków systemowych (“Remove references to undocumented system tables”)

Ostrzeżeń tych było zaledwie kilka i żaden z tych ostrzeżeń nie został oznaczony jako przypadek krytyczny, czyli taki, który mógłby sprawić, że kod po przełączeniu przestałby działać (“BreakingChange”)

Zmiana

  1. Wybór środowiska
    Zmiana powinna zostać szczegółowo zaplanowana. Według mnie najlepszą metodą jest zacząć od najniższego środowiska (DEV) i następnie przechodzić w górę (TEST, PREPROD, PROD). Oczywiście nie jest powiedziane, że problemy po zmianie będą identyczne na każdym środowisku, natomiast będziemy mogli się zorientować co może nas czekać przy najważniejszej zmianie, czyli zmianie na produkcji. Inna sprawa, że wszystkie środowiska muszą zostać przełączone. Musimy natomiast pamiętać, że deweloperzy, którze będą pisać nowe rzeczy powinni zadbać o kompatybilność swojego kodu między wersjami oraz o dodatkowe testy wydajnościowe.
  2. Poinformowanie zespołu
    Jak zaznaczono wyżej każda osoba, która pracuje z bazą danych na każdym środowisku dla którego ma być przeprowadzona zmiana powinien o tym wiedzieć. Po pierwsze, aby zadbać o kompatybilność nowego kodu, a po drugie, aby poinformować, że bieżący kod może działać wolniej lub inaczej niż było to do tej pory. Powinniśmy również zrozumieć czy inne zespoły, narzędzia lub serwisy nie korzystają z tych baz danych i w razie konieczności poinformować zainteresowanych o możliwych problemach.
  3. Zmiana trybu
    Przeprowadzenie samej zmiany jest bardzo proste i sprowadza się do kilku kroków.

    1. Zmiana trybu
      Aby wyświetlić bieżący tryb dla bazy danych można wykorzystać następujące zapytanie:

      SELECT 
      	d.name, 
      	d.compatibility_level
      FROM sys.databases d
      WHERE d.name = 'database_name'

      Aby zmienić tryb zalecanym jest najpierw przełączyć się w tryb “SINGLE_USER”, następnie zmienić tryb kompatybilności i powrócić do “MULTI_USER”

      USE master
      GO
      
      ALTER DATABASE [database_name]  
      SET SINGLE_USER     
      WITH ROLLBACK IMMEDIATE
      GO
      
      -- 110 - SQL Server 2012 
      -- 120 - SQL Server 2014, 
      -- 130 - SQL Server 2016
      ALTER DATABASE [database_name]  SET COMPATIBILITY_LEVEL = 120    
      GO
      
      ALTER DATABASE [database_name]  SET MULTI_USER
      GO

      Jest to zalecana technika i Microsoft ostrzega, że bez przełączenia się w tryb “SINGLE_USER” niektóre zapytania mogą korzystać z niewłaściwych planów. Jak widać, sama zmiana jest bardzo prosta i ogranicza się do wykonania kilku komend.

    2. Czyszczenie planów zapytania
      Niektóre miejsca w sieci podają, że w przypadku zmiany trybu zgodności procedury według potrzeby będą oznaczone do rekompilacji automatycznie. Z moich obserwacji wynika, że jest to nieprawda, lub nie zawsze tak jest. Moje testy wskazały, że mimo wszystko najbezpieczniejszą opcją będzie, zaraz po zmianie,  wyczyszczenie całego cache’a. 

      DBCC FREEPROCCACHE -- clear all plans in cache
      GO
      CHECKPOINT -- write dirty pages
      GO
      DBCC DROPCLEANBUFFERS -- Clear Buffer pool
      GO

      Można użyć zapytania, które po prostu oznaczy wszystkie procedury do rekompilacji w przypadku następnego uruchomienia – do znalezienia w Internecie. Wydaje mi się natomiast, że wyczyszczenie zupełnie cache’a w tym przypadku będzie również odpowiednie. W moim przypadku bez oznaczenia procedur i bez wyczyszczenia cache’a, po zmianie, proces ETL miał ogromne problemy. Dla niektórych procedur, które powinny wykonywać się około 3 minut, czas wykonywania uległ zmianie do kilku godzin. Po ponownej zmianie do poprzedniego trybu procedury działały poprawnie. Po wyczyszczeniu cache’a natomiast zdecydowana większość procedur posiadała spodziewany czas wykonywania – oprócz przypadków opisanych poniżej.


Obserwacja i monitorowanie

Bardzo ważnym jest umiejętne sprawdzanie oraz monitorowanie zmiany, aby być w stanie szybko sprawdzać czas wykonywania procedur i zapytań oraz parametrów serwera. Oprócz standardowych technik dobrze sprawdza się przygotowanie odpowiednich zapytań i wykresów – na przykład z wykorzystaniem języka R. Posiadając taki szablon można w prosty i szybki sposób porównać różne ładowania hurtowni. Przykład poniżej:

#install.packages("plotly")
#install.packages("RODBC")

## params:
pShowNames = 1    # 0 - show fake number IDs, 1 - show real object name 

pLoadsToCompare = '7476864 (new 11), 7470564 (new 10), 7440432 (new 9), 7434655 (new 8), 7428523 (new 7), 7422414 (new 6), 7410007 (new 5), 7403666 (new 4), 7391865 (new 1), 7385744 (new 2), 7397619 (new 3), 7187028 (old 1), 7171835 (old 2)'
pDifferenceInMinLimit = "3"
pRunType = '2'      #           1 - packages, 2 - procedures
pNotEndedDuration = '200'       # in case that the run has been terminated put the fake long execution time to see this sp
pObjectRunInMinLimit = 'null'

# connection
serverAddress = 'server_address,server_port'
databaseName = 'database_with_log_name'
## end of params

################################################
# check object name by id:
#as.character((res[res$IndexName == "26","objectName"])[1])
################################################

library(plotly)
library(RODBC)

myConnectionString <- paste('driver=SQL Server;server=',serverAddress,';database=',databaseName,';trusted_connection=true', sep="")
mySqlQuery <- paste("database_name.schema_name.sp_name @LoadIDsToCompare = \'"
                    ,  pLoadsToCompare 
                    , '\', @DifferenceInMinLimit = '
                    , pDifferenceInMinLimit
                    , ', @RunType = '
                    , pRunType
                    , ', @NotEndedDuration = '
                    , pNotEndedDuration
                    , ', @ObjectRunInMinLimit = '
                    , pObjectRunInMinLimit
)

dbhandle <- odbcDriverConnect(myConnectionString)
res <- sqlQuery(dbhandle, mySqlQuery)
close(dbhandle)

{
  if (pShowNames == 0) {
    res["ChartName"] <- res$IndexName
  } else {
    res["ChartName"] <- res$objectName
  }
}

ggplot(res, aes(factor(ChartName), Duration_SUM, fill = as.factor(packageDescriptionFull))) + 
  geom_bar(stat="identity", position = "dodge") + 
  #scale_fill_brewer(palette = "Set1") +
  scale_fill_manual(values=res$PackageDescriptionShortLegend) +
  theme(axis.text.x=element_text(angle=50,hjust=1)) +
  geom_text(aes(label=Duration_SUM), size=3, position=position_dodge(width=0.9), vjust=-0.25) +
  guides(fill=guide_legend(title="Load ID and description")) +
  labs(x = paste(toString(res$RunType[1])," name")) +
  labs(y = "Duration") +
  ggtitle(paste(res$RunType[1], "run statistics", " (n = ", nrow(res) ,")")) +
  theme(legend.position="top")


Wykres pokaże różnice w kolejnych ładowaniach dla poszczególnych obiektów. Dzięki filtrowi można pokazać tylko te obiekty dla których różnica pomiędzy najszybszym i najwolniejszym wykonaniem była większa niż X minut. Kolory pokazują ładowania przed i po zmianie trybu zgodności.

Kolejny przykład przyda się w przypadku porównywania pojedynczych procedur lub pakietów:

library(plotly)
library(RODBC)
library(RColorBrewer)

serverAddress = 'server_name,server_port' 
databaseName = 'database_log_name'

myConnectionString <- paste('driver=SQL Server;server=',serverAddress,';database=',databaseName,';trusted_connection=true', sep="")
mySqlQuery <- paste("
                    
                    DECLARE @procName VARCHAR(200) = 'stored_procedure_name'
                    
                    ;WITH cte AS
                    (
                    SELECT 
                    DATEDIFF(SECOND, startTime, ISNULL(endTime, GETUTCDATE())) AS RunDuration,
                    a.startTime AS StartTimeDateTime,
                    a.endTime AS EndTimeDateTime,
                    ROW_NUMBER() OVER(PARTITION BY CONVERT(DATE,a.startTime) ORDER BY a.startTime) AS RN,
                    CONVERT(DATE,a.startTime) AS StartTimeDate,
                    a.procRunLogID,
                    CASE 
                    WHEN CONVERT(DATE,a.startTime) <= '2017-09-13' THEN 'OLD'
                    ELSE 'NEW'
                    END AS 'CompatibilityMode'
                    FROM table_with_log_name a
                    WHERE procName = @procName
                    AND a.endTime IS NOT null
                    )
                    SELECT 
                    cte.StartTimeDate AS CalendarDate,
                    @procName AS ProcName,
                    AVG(cte.RunDuration) AS RunDuration_AVG_SECONDS,
                    MIN(cte.RunDuration) AS RunDuration_MIN_MINUTES,
                    MAX(cte.RunDuration) AS RunDuration_MAX_MINUTES,
                    AVG(cte.RunDuration) AS RunDuration_AVG_MINUTES,
                    SUM(cte.RunDuration) AS RunDuration_SUM_MINUTES,
                    MIN(cte.CompatibilityMode) AS CompatibilityMode,
                    COUNT(*) AS CNT
                    FROM cte
                    GROUP BY cte.StartTimeDate
                    ORDER BY cte.StartTimeDate DESC 
                    "
)

dbhandle <- odbcDriverConnect(myConnectionString)
res <- sqlQuery(dbhandle, mySqlQuery)
close(dbhandle)

p <- plot_ly(
  x = res$CalendarDate,
  y = res$RunDuration_AVG_SECONDS, 
  color = res$CompatibilityMode,
  colors = c("#b60c0c", "#062452"),
  text = paste(
    "start time: ", res$CalendarDate,
    "<BR>Proc name: ", res$ProcName,
    "<BR>Run Duration AVG SECONDS: ", res$RunDuration_AVG_SECONDS,
    "<BR>Run Duration MIN MINUTES: ", floor(res$RunDuration_MIN_MINUTES/60 * 100) / 100,
    "<BR>Run Duration MAX MINUTES: ", floor(res$RunDuration_MAX_MINUTES/60 * 100) / 100,
    "<BR>Run Duration AVG MINUTES: ", floor(res$RunDuration_AVG_MINUTES/60 * 100) / 100,
    "<BR>Run Duration SUM MINUTES: ", floor(res$RunDuration_SUM_MINUTES/60 * 100) / 100,
    "<BR>Compatibility mode: ", res$CompatibilityMode,
    "<BR>Number of executions: ", res$CNT
  ),
  type = "bar") %>%
  layout(
    margin = list(l = 50, r = 50, b = 80, t = 50, pad = 4), 
    xaxis = list(tickangle = 45),
    barmode = 'group'
  ) %>%
  add_trace(y = res$RunDuration_SUM_MINUTES, name = 'SUM', marker = list(color = '#C9EFF9'))
p

W tym przypadku wykres pokazuje poszczególne wykonania procedury. Znowu z podziałem na wywołania przed i po zmianie.

Oczywiście, aby wykorzystać te skrypty trzeba posiadać odpowiednie logowanie oraz przygotować odpowiednie zapytania, natomiast pozwoli to zaoszczędzić sporo czasu podczas monitorowania.

Plan awaryjny

Zmiana może spowodować, że niektóre zapytania lub procedury znacznie spowolnią. Szczególnie przed zmianą na serwerze produkcyjnym trzeba mieć zatem plan B.

  1. Źródło problemu – na samym początku musimy zdać sobie sprawę, że coś jest nie tak. Oczywiście w pierwszej kolejności musimy monitorować podstawowe charakterystyki pracy serwera oraz monitorować to, co aktualnie dzieje się na serwerze. Możemy też wykorzystać różne narzędzia (np. RedGate) lub własne rozwiązania do monitorowania oraz logowania. W tym przypadku będziemy szukać zapytań, które po zmianie stały się wolniejsze.
  2. Zmiana trybu dla pojedynczej procedury  – w przypadku, gdy pojedyncza procedura wykonuje się wolno możemy skorzystać z flagi i wymusić tryb dla całej procedury.
    DBCC TRACEON (2312); -- 120 
    DBCC TRACEON (9481); -- 110
  3. Zmiana trybu dla pojedynczego zapytania – możemy również wymusić konkretny tryb dla pojedynczego zapytania
    option (QUERYTRACEON (2312); 
    option (QUERYTRACEON (9481);

    Dzięki temu, po zmianie, możemy wymusić poprzedni tryb zgodności tylko dla wybranych obiektów, natomiast wszystkie pozostałe będą korzystać z nowej wersji silnika.

Napotkane problemy

Osobiście spotkałem się z następującymi problemami:

  1. Zmienne tabelaryczne – o zmiennych tabelarycznych napisano i powiedziano już naprawdę wiele. W tym przypadku zmienna tabelaryczna przechowywała tylko kilka wierszy, natomiast w przypadku kilku procedur powodowało to ogromne problemy dla nowej wersji silnika.
    Przykładowy fragment kodu wyglądał tak:

    DECLARE @List TABLE
    ( 
    col SMALLINT
    )
    
    INSERT INTO @List
    (
    col
    )
    
    SELECT
    col
    FROM tdw.dbo.getsList(@IdList)
    

    Następnie zmienna tabelaryczna była wykorzystywana w kolejnych zapytaniach, aby przefiltrować wiersze. Po zmianie niektóre procedury znacznie spowolniły. Lepszym wariantem będzie wykorzystanie tabeli tymczasowej:

    CREATE TABLE #List
    ( 
    col SMALLINT
    )
    
    INSERT INTO #List
    ( 
    col
    )
    
    SELECT
    sk_brand 
    FROM tdw.dbo.getskList(@IdList)
    

    poniżej plany zapytania:

    Zamiana zmiennych tabelarycznych na tabele tymczasowe przyniosła zauważalne zmiany w niektórych procedurach.

  2. Niewłaściwe “hinty”
    Z pewnych względów niektóre “hinty” przestały działać i powodowały spadek wydajności. Zauważyłem problemy z:

    OPTION (FORCE ORDER)
    Inner Loop Join

    Po usunięciu tych “hintów” zapytania zaczęły znowu działać poprawnie.

  3. Niepokrywające indeksy
    Zmiana trybu poprawności uwypukliła również niepokrywające indeksy. Poniższy przykład prezentuje plan zapytania, dla dokładnie takiego samego zapytania (OLD, NEW). W przypadku “compatibility mode” ustawionego dla SQL Server 2014 zapytanie znacznie spowolniło.

    Problemem okazał się niepokrywający indeks. Będąc dokładnym to tabela posiadała prawidłowy indeks, natomiast zapytanie nie wykorzystywało wszystkich kolumn indeksów tak jak powinno. Po zapewnieniu odpowiedniego zapytania czas wykonywania uległ skróceniu.

  4. Intra-Query Parallel Thread Deadlocks
    W przypadku jednej z procedur wystąpił również problem z Intra-Query Parallel Thread Deadlocks, czyli problem z wielowątkowością, co pokazał odpowiedni deadlock. Deadlocki zostały wychwycone przez narzędzia do monitorowania, natomiast możemy również wykorzystać, na przykład, Extended Events:

    CREATE EVENT SESSION [DeadlockTrace] ON SERVER ADD EVENT sqlserver.lock_deadlock
    (    
        ACTION
        (
            package0.collect_system_time,
            package0.event_sequence,
            package0.process_id,
            sqlserver.database_name,
            sqlserver.plan_handle,
            sqlserver.request_id,
            sqlserver.session_id,
            sqlserver.sql_text,
            sqlserver.tsql_stack
        )
    ),
    ADD EVENT sqlserver.lock_deadlock_chain
    (    
        ACTION
        (
            package0.collect_system_time,
            package0.event_sequence,
            package0.process_id,
            sqlserver.database_name,
            sqlserver.plan_handle,
            sqlserver.request_id,
            sqlserver.session_id,
            sqlserver.sql_text,
            sqlserver.tsql_stack
        )
    )
    ,ADD EVENT sqlserver.xml_deadlock_report
    (    
        ACTION
        (
            package0.collect_system_time,
            package0.event_sequence,
            package0.process_id,
            sqlserver.database_name,
            sqlserver.plan_handle,
            sqlserver.request_id,
            sqlserver.session_id,
            sqlserver.sql_text,
            sqlserver.tsql_stack
        )
    )
    ADD TARGET package0.event_file
    (
        SET filename=N'G:\log.02\Deadlock\DeadlockFile.xel'),
        ADD TARGET package0.ring_buffer(SET max_events_limit=(10000),
        occurrence_number=(50000)
    )
    WITH
    (
    MAX_MEMORY=4096 KB,
    EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
    MAX_DISPATCH_LATENCY=30 SECONDS,
    MAX_EVENT_SIZE=0 KB,
    MEMORY_PARTITION_MODE=NONE,
    TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF
    )
    GO
    

    Do sprawdzenia wyników:

    WITH cte AS
    (
        SELECT
            XEvent.query('(event/data/value/deadlock)[1]') AS DeadlockGraph, 
            CONVERT( nvarchar(MAX),  XEvent.query('(event/data/value/deadlock)[1]')) AS DeadlockGraphTxt
        FROM
        (    
            SELECT
                XEvent.query('.') AS XEvent    
            FROM
            (        
                SELECT
                    CAST(target_data AS XML) AS TargetData        
                    FROM sys.dm_xe_session_targets st        
                    INNER JOIN sys.dm_xe_sessions s 
                        ON s.address = st.event_session_address        
                    WHERE
                        s.NAME = 'DeadlockTrace'           
                        AND st.target_name = 'ring_buffer'       
            ) AS Data
            CROSS APPLY TargetData.nodes('RingBufferTarget/event[@name="xml_deadlock_report"]') AS XEventData(XEvent)
    ) AS source
    )
    SELECT
        *, 
        SUBSTRING(DeadlockGraphTxt,CHARINDEX('procname="',DeadlockGraphTxt)+10, CHARINDEX('"',SUBSTRING(DeadlockGraphTxt,CHARINDEX('procname="',DeadlockGraphTxt)+10, 500)) -1)
    FROM cte
    

    Poniżej schemat “deadlocka”:

    Rozwiązaniem problemy w tym przypadku była zmiana zapytania na bardziej wydajne. Alternatywnie moglibyśmy skorzystać z ustawienia MAXDOP dla tego zapytania.

  5. Problem z Cross Apply
    Nowy “Cardinality Estimator” z pewnych względów zaczął mieć również problemy z cross apply. Pomijając treść samego zapytania po zamianie cross-apply na dodatkowe zapytanie oraz tabelę tymczasową zapytanie znacząco przyspieszyło.

    Powyżej statystyki dla zapytania z cross-apply oraz dla nowej wersji zapytania.

  6. Problem ze zmianą typu danych dla partycji
    Po zmianie trybu zgodności poniższe zapytanie zwracało błąd:

    SELECT
        boundary_id + 1, 
        prv.value 
    FROM TDW.sys.partition_range_values prv 
    INNER JOIN TDW.sys.partition_functions pf 
        ON prv.function_id = pf.function_id 
    WHERE
        pf.name = 'partitionDayFunction'
        AND (CAST(prv.value AS Date) IN (SELECT CAST(DATEADD(DAY, -6, GETUTCDATE()) AS Date)))
    

    Problem dotyczył konwertowania wartości funkcji z typu SQL_Variant na datę.

    SELECT
        boundary_id + 1, 
        prv.value 
    FROM TDW.sys.partition_range_values prv 
    INNER JOIN TDW.sys.partition_functions pf 
        ON prv.function_id = pf.function_id 
    WHERE 
        pf.name = 'partitionDayFunction'
        AND CAST(DATEADD(DAY, -6, GETUTCDATE()) AS DATE) = CASE WHEN pf.name = 'partitionDayFunction' THEN CAST(prv.value AS DATE) END
    

    W rozwiązaniu problemu pomógł Adam Machanic:

    Co ciekawe, przed zmianą, procedury z podobnym kodem nigdy nie zwróciły błędu, natomiast po zmianie procedury zawsze kończyły się błędem.

  7. Merge
    Odnośnie mergy planowana jest dłuższa seria… Z całą pewnością natomiast niektóre procedury z klauzulą “Merge” po zmianie trybu zgodności z 110 na 120 spowolniły. Dla niektórych zmiana była dość znaczna i zapytania trzeba było przepisać.

Podsumowanie

Zmiana trybu zgodności dla SQL Server jest zmianą bardzo prostą i szybką, natomiast w niektórych sytuacjach może przysporzyć wielu problemów. W celu zniwelowania potencjalnych problemów w środowisku produkcyjnym trzeba mieć dobry plan, dobrze rozumieć potencjalne problemy i wiedzieć jak na nie reagować. Mam nadzieję, że ten post oraz “mój” plan pomoże Wam podczas Waszych przygód ze zmianą trybu zgodności lub instalacją nowej wersji SQL Server.

Dodatek

Od wersji SQL Server 2016 warto również przyjrzeć się ALTER DATABASE SCOPED CONFIGURATION https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-database-scoped-configuration-transact-sql

Slawomir Drzymala
Follow me on

1 Comment

  1. Panie Sławomirze mam pytanie odnośnie zmiany CL w przypadku klastra Always-On

    Czy zmieniam CL na primary replika i czekam aż zmiany “przejeda” na pozostałe repliki czy może jest jakaś inna procedura

Leave a Reply