Niektóre interesujące dyskusje zawsze toczą się wokół tematu rozszczepiania strun. Mam nadzieję, że w dwóch poprzednich wpisach na blogu:„Podziel ciągi we właściwy sposób – lub następny najlepszy sposób” i „Podział ciągów:kontynuacja”, pokazałem, że ściganie „najlepiej działającej” funkcji podziału T-SQL jest bezowocne. . Kiedy podział jest rzeczywiście konieczny, CLR zawsze wygrywa, a następna najlepsza opcja może się różnić w zależności od aktualnego zadania. Ale w tych postach zasugerowałem, że dzielenie po stronie bazy danych może nie być konieczne.
W SQL Server 2008 wprowadzono parametry wyceniane w tabeli, sposób przekazywania „tabeli” z aplikacji do procedury składowanej bez konieczności budowania i analizowania ciągu, serializacji do XML lub zajmowania się jakąkolwiek z metod dzielenia. Pomyślałem więc, że sprawdzę, jak ta metoda wypada na tle zwycięzcy naszych poprzednich testów – ponieważ może to być realna opcja, niezależnie od tego, czy można używać CLR, czy nie. (Aby zapoznać się z ostateczną biblią na temat TVP, zapoznaj się z obszernym artykułem innego MVP SQL Server, Erlanda Sommarskoga).
Testy
Na potrzeby tego testu zamierzam udawać, że mamy do czynienia z zestawem ciągów wersji. Wyobraź sobie aplikację C#, która przekazuje zestaw tych ciągów (powiedzmy, które zostały zebrane od zbioru użytkowników) i musimy dopasować wersje do tabeli (powiedzmy, która wskazuje wydania usług, które mają zastosowanie do określonego zbioru wersji). Oczywiście prawdziwa aplikacja miałaby więcej kolumn niż to, ale tylko po to, aby utworzyć pewną objętość i nadal utrzymywać chudą tabelę (używam również NVARCHAR, ponieważ to właśnie zajmuje funkcja podziału CLR i chcę wyeliminować wszelkie niejednoznaczności z powodu niejawnej konwersji) :
CREATE TABLE dbo.VersionStrings(left_post NVARCHAR(5), right_post NVARCHAR(5)); UTWÓRZ SKLASTROWANY INDEKS x ON dbo.VersionStrings(left_post, right_post);;WITH x AS ( SELECT lp =CONVERT (DZIESIĘTNY(4,3), RIGHT(RTRIM(s1.[object_id]), 3)/1000.0) FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2)INSERT dbo. VersionStrings( left_post, right_post)SELECT lp - CASE WHEN lp> =0.9 THEN 0.1 ELSE 0 END, lp + (0.1 * CASE WHEN lp> =0.9 THEN -1 ELSE 1 END)FROM x;
Teraz, gdy dane są na swoim miejscu, następną rzeczą, którą musimy zrobić, jest utworzenie typu tabeli zdefiniowanej przez użytkownika, który może przechowywać zestaw ciągów. Początkowy typ tabeli do przechowywania tego ciągu jest dość prosty:
CREATE TYPE dbo.VersionStringsTVP AS TABLE (VersionString NVARCHAR(5));
Następnie potrzebujemy kilku procedur składowanych, aby zaakceptować listy z C#. Dla uproszczenia, ponownie, po prostu weźmiemy pod uwagę liczbę, aby mieć pewność, że wykonamy pełne skanowanie, i zignorujemy liczbę w aplikacji:
CREATE PROCEDURE dbo.SplitTest_UsingCLR @list NVARCHAR(MAX)ASBEGIN SET NOCOUNT ON; SELECT c =COUNT(*) FROM dbo.VersionStrings AS v INNER JOIN dbo.SplitStrings_CLR(@list, N',') AS s ON s.Item BETWEEN v.left_post AND v.right_post;ENDGO CREATE PROCEDURE dbo.SplitTestest_Us dbo.VersionStringsTVP READONLYASBEGIN USTAW NOCOUNT ON; SELECT c =COUNT(*) FROM dbo.VersionStrings AS v INNER JOIN @list AS l ON l.VersionString BETWEEN v.left_post AND v.right_post;ENDGO
Zwróć uwagę, że TVP przekazany do procedury składowanej musi być oznaczony jako READONLY — obecnie nie ma możliwości wykonania DML na danych, tak jak w przypadku zmiennej tabeli lub tabeli tymczasowej. Jednak Erland zgłosił bardzo popularną prośbę, aby Microsoft uelastycznił te parametry (i dużo głębszego wglądu w jego argumentację).
Piękno polega na tym, że SQL Server nie musi już w ogóle zajmować się dzieleniem ciągu – ani w T-SQL, ani przekazywaniem go do CLR – ponieważ jest już w ustalonej strukturze, w której się wyróżnia.
Następnie aplikacja konsolowa C#, która wykonuje następujące czynności:
- Przyjmuje liczbę jako argument wskazujący, ile elementów ciągu należy zdefiniować
- Buduje ciąg CSV tych elementów, używając StringBuilder, w celu przekazania do procedury składowanej CLR
- Buduje DataTable z tymi samymi elementami, które mają być przekazane do procedury składowanej TVP
- Przed wywołaniem odpowiednich procedur składowanych testuje również obciążenie związane z konwersją ciągu CSV do DataTable i odwrotnie.
Kod aplikacji C# znajduje się na końcu artykułu. Potrafię przeliterować C#, ale w żadnym wypadku nie jestem guru; Jestem pewien, że można tam zauważyć nieefektywności, które mogą sprawić, że kod będzie działał nieco lepiej. Jednak wszelkie takie zmiany powinny w podobny sposób wpłynąć na cały zestaw testów.
Uruchomiłem aplikację 10 razy używając 100, 1000, 2500 i 5000 elementów. Wyniki były następujące (pokazuje średni czas trwania w sekundach dla 10 testów):
Wydajność na bok…
Oprócz wyraźnej różnicy w wydajności, TVP mają jeszcze jedną zaletę — typy tabel są znacznie prostsze do wdrożenia niż zestawy CLR, szczególnie w środowiskach, w których CLR jest zabronione z innych powodów. Mam nadzieję, że bariery dla CLR stopniowo znikną, a nowe narzędzia sprawią, że wdrożenie i utrzymanie stanie się mniej bolesne, ale wątpię, aby łatwość początkowego wdrożenia dla CLR kiedykolwiek była łatwiejsza niż podejścia natywne.
Z drugiej strony, oprócz ograniczenia tylko do odczytu, typy tabel są podobne do typów aliasów, ponieważ trudno je modyfikować po fakcie. Jeśli chcesz zmienić rozmiar kolumny lub dodać kolumnę, nie ma polecenia ALTER TYPE, a aby usunąć typ i ponownie go utworzyć, musisz najpierw usunąć odniesienia do typu ze wszystkich procedur, które go używają . Na przykład w powyższym przypadku, gdybyśmy musieli zwiększyć kolumnę VersionString do NVARCHAR(32), musielibyśmy utworzyć typ fikcyjny i zmienić procedurę składowaną (i każdą inną procedurę, która z niej korzysta):
CREATE TYPE dbo.VersionStringsTVPCopy AS TABLE (VersionString NVARCHAR(32));GO ALTER PROCEDURE dbo.SplitTest_UsingTVP @list dbo.VersionStringsTVPCopy READONLYAS...GO DROP TYPE dbo.VersionGOString dbo. NVARCHAR(32));GO ALTER PROCEDURE dbo.SplitTest_UsingTVP @list dbo.VersionStringsTVP READONLYAS...GO TYP UPUSZCZENIA dbo.VersionStringsTVPCopy;GO
(Lub alternatywnie upuść procedurę, upuść typ, ponownie utwórz typ i ponownie utwórz procedurę.)
Wniosek
Metoda TVP konsekwentnie przewyższała metodę dzielenia CLR i to o większy procent wraz ze wzrostem liczby elementów. Nawet dodanie narzutu związanego z konwersją istniejącego ciągu CSV do DataTable dało znacznie lepszą wydajność od końca do końca. Mam więc nadzieję, że jeśli wcześniej nie przekonałem Cię do porzucenia technik dzielenia ciągów w T-SQL na rzecz CLR, zachęciłbym Cię do wypróbowania parametrów wycenianych w tabeli. Testowanie powinno być łatwe, nawet jeśli obecnie nie używasz DataTable (lub jakiegoś odpowiednika).
Kod C# używany w tych testach
Jak powiedziałem, nie jestem guru C#, więc prawdopodobnie robię tu wiele naiwnych rzeczy, ale metodologia powinna być całkiem jasna.