porównanie wydajności metod dostępu do baz sql w

Transkrypt

porównanie wydajności metod dostępu do baz sql w
PORÓWNANIE WYDAJNOŚCI METOD DOSTĘPU
DO BAZ SQL W ŚRODOWISKU .NET
Mirosław Kordos i Justyna Żynda
Akademia Techniczno-Humanistyczna w Bielsku-Białej
Streszczenie. Artykuł zawiera przegląd i analizę metod dostępu do relacyjnej bazy
danych na serwerach MS SQL Server 2008R2 oraz MS SQL Server 2011 CTP1 ze
środowiska .NET 4.0 oraz porównanie ich wydajności. Zaprezentowano w nim opis
poszczególnych metod, omówienie aplikacji testowej oraz wyniki przeprowadzonych testów
i wnioski z nich wynikające.
Słowa kluczowe: bazy danych, ORM
COMPARISON OF DATABASE ACCESS METHOD
PERFORMANCE IN .NET FRAMEWORK
Abstract. Article presents overview and analysis of MS SQL Server 2008R2 and MS
SQL Server 2011 CTP1 databases access methods in .NET 4.0 framework as well as their
performance comparison. Additionally, this paper contains description of individual
methods, review of test application and results of conducted measurements as well as
conclusions.
Keywords: databases, ORM
Wprowadzenie
Do korzystania z najpopularniejszych relacyjnych baz danych można użyć kilku metod, których
działanie nie ogranicza się tylko do przekazywania poleceń do silnika bazy lub pobierania danych.
Język C# niejako wymusza pisanie oprogramowania w sposób obiektowo zorientowany, dla którego
występuje zjawisko impedancji obiektowo-relacyjnej (object-relational impedance mismatch) –
rozwiązaniem tego problemu są metody realizujące mapowanie obiektowo-relacyjne (ORM –
object-relational mapping). Umożliwiają one pracę na relacyjnie składowanych danych
udostępniając je jako obiekty. Korzystanie z nich pociąga za sobą wykonywanie dodatkowego kodu
odpowiedzialnego za przekształcenie struktury relacyjnej na obiektową (i na odwrót) oraz za
tłumaczenie zapytań definiowanych w kodzie, na przykład za pomocą LINQ, na język SQL –
natywny dla silników bazodanowych. Oprócz obiektowej struktury, zaletami metod realizujących
ORM jest między innymi samodzielne zarządzanie przez nie połączeniami do bazy danych. Są one
uważane za wygodniejsze, ale niekoniecznie równie wydajne jak metody bardziej „bezpośrednie”.
Porównywane metody
ADO.NET
ADO.NET jest częścią platformy .NET. Dostarcza narzędzia umożliwiające podłączenie do
bazy danych, wykonywanie komend oraz pozyskiwanie danych. ADO.NET składa się z dwóch
głównych komponentów: dostawcy danych (Data Provider) i obiektów Data Set.
Rysunek 1: Architektura ADO.NET
Dostawca danych odpowiada za podłączenie do bazy danych i manipulację danymi. W skład
dostawcy danych wchodzą cztery klasy. Połączenie z bazą danych nawiązuje i zamyka obiekt klasy
Connection. Do wykonywania zapytań, przesyłania instrukcji i wywoływania procedur
składowanych służy klasa Command. Obiekt klasy DataReader dostarcza odczytywany wierszami
strumień danych. DataAdapter to klasa która umożliwia zapisanie danych do obiektu Data Set,
który umożliwia przechowywanie danych bez utrzymywania połączenia z bazą. Data Set zawiera
obiekty Data Table – posiadają one strukturę podobną do tabel w relacyjnej bazie danych.
W ADO.NET możliwe są dwa sposoby dostępu do danych: z otwartym połączeniem
i odłączony. W tym pierwszym aplikacja korzysta z danych dostarczonych bezpośrednio przez
dostawcę danych, natomiast w sposobie odłączonym dostawca danych przekazuje dane do obiektu
Data Set.
LINQ TO SQL Data Classes
LINQ TO SQL jest częścią platformy .NET realizującą mapowanie obiektowo-relacyjne.
Zapytania do bazy danych definiowane są jako integralna część kodu programu w języku LINQ.
Podczas działania programu LINQ TO SQL tłumaczy je na kod SQL i wysyła do bazy danych,
zwrócone wyniki przekształcane są na obiekty którymi można manipulować. LINQ TO SQL
umożliwia także wywoływanie procedur składowanych.
Źródło danych reprezentuje obiekt DataContext, poszczególne tabele - wygenerowane klasy.
ADO.NET Entity Framework
Podobnie jak LINQ TO SQL, Entity Framework jest narzędziem ORM – udostępnia fizyczny
schemat bazy danych w postaci abstrakcyjnego schematu pojęciowego, niwelując impedancję
pomiędzy relacyjną bazą danych i obiektowo-zorientowanym językiem programowania. W
przeciwieństwie do LINQ TO SQL, które mapuje schemat bazy danych na klasy w sposób
bezpośredni, Entity Framework umożliwia stworzenie modelu danych luźno sprzężonego ze
schematem bazy. Przykładowo możliwe jest mapowanie kilku tabel do pojedynczej klasy i
odwrotnie. Kolejną właściwością Entity Framework, której brakuje LINQ TO SQL, jest
bezpośrednia obsługa relacji wiele do wiele.
Obiekt ObjectContext umożliwia wykonywanie manipulacji danymi poprzez obiekty encji.
Aplikacja testowa
Dla potrzeb aplikacji zaprojektowano trzy schematy baz danych. Pierwszy stanowiący
uproszczony model systemu zamówień, drugi abstrakcyjny, z niewielką ilością tabel i relacji oraz
trzeci analogiczny z drugim ale ze zwiększoną ilością tabel i relacji. Bazy zostały wypełnione
danymi stworzonymi w sposób losowy, w oparciu o generator liczb pseudo-losowych
zaimplementowany w bibliotekach systemowych .NET – klasę Random.
Zapytania SELECT realizowane są przy użyciu klasy Data Reader (poprzez zapytanie oraz
wywołanie procedury składowanej), klasy Data Set (poprzez zapytanie oraz wywołanie procedury
składowanej), klas mapujących LINQ TO SQL i metody reprezentującej procedurę składowaną oraz
encji Entity Framework.
Zapytania INSERT wykonywane są przy pomocy klasy Data Reader (poprzez zapytanie oraz
wywołanie procedury składowanej), klas mapujących LINQ TO SQL i metody reprezentującej
procedurę składowaną oraz encji Entity Framework.
Zapytania UPDATE wykonywane są przy pomocy klasy Data Reader (poprzez zapytanie oraz
wywołanie procedury składowanej), klas mapujących LINQ TO SQL i metody reprezentującej
procedurę składowaną oraz encji Entity Framework.
Do pomiaru czasu wykonywania poszczególnych zapytań użyto klasy StopWatch z przestrzeni
nazw System.Diagnostics. Przed każdym pomiarem tworzono obiekt klasy StopWatch, pomiar
rozpoczynając wywołaniem metody Start a kończąc metodą Stop. Czas pomiaru wskazuje
właściwość Elapsed.
Pod uwagę brano tylko sam czas wykonywania zapytania. Czas trwania nawiązywania
połączenia, tworzenia obiektów niezbędnych do wykonania komendy, generowania danych nie był
mierzony.
Testy przeprowadzono na komputerze z 32 bitowym systemem Windows 7, procesorem Intel
Pentium 4 3,00 GHz, dwoma dyskami ATA 70 i 20 GB. Zaprezentowane wyniki pochodzą z testów
na MS SQL Server 2008 R2, wyniki testów przeprowadzonych na MS SQL Server 2011 CTP1 były
zbliżone.
Analiza wyników
Zapytania SELECT
Zapytanie pierwsze
Zapytanie pierwsze zwraca wszystkie dane z tabeli. W przypadku schematu pierwszego z tabeli
DB1Klienci, w drugim schemacie z tabeli DB2T1 oraz w trzecim z tabeli DB3T1. Zapytanie
w SQL dla pierwszego schematu bazy:
SELECT * FROM DB1Klienci;
Odpowiadające mu zapytanie LINQ:
List<DB1Klienci> klienci = (from k in dc.DB1Kliencis
select k).ToList();
W tym przypadku dla każdego stanu bazy DB1 najszybsze okazały się metody Data Reader oraz
Data Reader wywołujący procedurę składowaną. Średnie geometryczne czasów ich wykonywania
są do siebie bardzo zbliżone, odchylenia standardowe również są niewielkie. Trzecie w kolejności
najszybszego wykonywania jest wywoływanie procedury składowanej za pomocą LINQ TO SQL.
Następnie bardzo zbliżone do siebie czasami wykonywania dla większości stanów bazy danych
LINQ TO SQL, Data Adapter i wywoływanie przez niego procedury składowanej. Dla większości
stanów bazy danych najwolniej radziło sobie Entity Framework, ale dla 1024000 wierszy w tabeli
DB1Klient jego miejsce zajął Data Adapter, którego średnia czasów wykonywania gwałtownie
wzrosła, wzrosło również odchylenie standardowe co wskazuje na duży rozrzut czasów
wykonywania przez niego zapytania.
Dla bazy DB2 czasy wykonywania zapytania pierwszego są niższe, zmniejszyły się także
różnice pomiędzy szybkością działania poszczególnych metod – dla 64000 i 128000 wierszy
różnica między szybkością działania najszybszej metody DataReader i wywoływanej przez nią
procedury składowanej a pozostałymi metodami bardzo się zmniejszyła. Spadek wydajności był
stały na co wskazuje brak wzrostu odchylenia standardowego. Niezmieniona w stosunku do
pomiarów dla DB1 pozostała kolejność metod od najszybszej do najwolniejszej: metody
DataAdapter + DataSet, wywoływana przez nie procedura składowana oraz LINQ TO SL uzyskały
zbliżone czasy, trochę lepiej radziła sobie procedura składowana wywoływana przez LINQ TO
SQL. Najgorzej wypadło Entity Framework. Różnice pomiędzy działaniem najszybszej i
najwolniejszej metody wynoszą od 1 do 25 sekund.
Czas w sekundach (średnia geometryczna)
100
DATA_ADAPTER_DATA_SET
DATA_ADAPTER_DATA_SET_SPR
OC
DATA_READER
DATA_READER_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
10
1
0,1
32000
64000
128000
256000
512000
1024000
Ilość wierszy w tabeli Klient
Rysunek 2: Wykres czasów wykonywania pierwszego zapytania SELECT dla bazy DB1
Czas w sekundach (średnia geometryczna)
100
DATA_ADAPTER_DATA_SE
T
DATA_ADAPTER_DATA_SE
T_SPROC
DATA_READER
DATA_READER_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
10
1
0,1
0,01
32000
64000
128000
256000
512000 1024000
Ilość wierszy w tabeli T1
Rysunek 3: Wykres średnich geometrycznych czasów zapytania SELECT typu pierwszego
dla bazy DB2
Dla bazy DB3 czasy wykonywania zapytania pierwszego są najwyższe. Podobnie jak w dwóch
poprzednich schematach najlepiej wypadła metoda DataReader i wywoływana przez nią procedura
składowana. Następną najbardziej wydajną metodą jest procedura składowana wywoływana przez
LINQ TO SQL ale dla większej ilości wierszy w bazie jej miejsce zajmuje Entity Framework, który
dla mniejszej ilości wierszy wypada najgorzej. LINQ TO SQL, DataAdpater z DataSet oraz
procedura składowana przez niego wywoływana wypadają najgorzej dla większej ilości wierszy.
Różnice między najlepszym a najgorszym wynikiem wynoszą od 1,9 sekundy do 203 sekund. Wraz
Czas w sekundach (średnia geometryczna)
ze wzrostem wierszy w bazie odnotowano zdecydowany wzrost odchylenia standardowego. Ta
nierównomierność w najmniejszym stopniu dotyczyła metody Data Reader oraz wywoływanej
przez nią procedury składowanej.
1000
DATA_ADAPTER_DATA_SE
T
DATA_ADAPTER_DATA_SE
T_SPROC
DATA_READER
DATA_READER_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
100
10
1
0,1
32000
64000
128000
256000
512000
Ilość wierszy w tabeli T1
Rysunek 4: Wykres średnich geometrycznych czasów wykonywania pierwszego zapytania
SELECT dla bazy DB3
Zapytanie drugie
Drugie zapytanie łączy dwie tabele i zlicza ilość wierszy dla których istnieje powiązanie z daną
wartością. Wartości są zliczane dla wierszy spełniających zdefiniowany warunek. Dla tabel z
pierwszego schematu zapytanie łączy tabelę DB1Produkty z tabelą DB1ZamowieniaPozycje
i zwraca nazwy produktów których ceny jednostkowe są większe od 100 oraz ilość pozycji
zamówień na których znajduje się dany produkt. W SQL:
SELECT DB1Produkty.nazwa, count(id_pozycja)
FROM DB1ZamowieniaPozycje
JOIN DB1Produkty ON DB1ZamowieniaPozycje.id_produkt = DB1Produkty.id_produkt
WHERE DB1Produkty.cena_jednostkowa > 100
GROUP BY DB1Produkty.id_produkt, DB1Produkty.nazwa;
oraz w LINQ:
var produkty = (from p in dc.DB1Produkties
where p.cena_jednostkowa > 100
group p by p.id_produkt into g
select
new { nazwa = g.Select(y => y.nazwa),
ilosc = g.Select(x => x.DB1ZamowieniaPozycjes.Select(z => z.id_pozycja)
.Count()) }).ToList();
Czas w sekundach (średnia geometryczna)
Dla drugiego i trzeciego schematu tabelom DB1Produkty i DB1ZamowieniaPozycje
odpowiadają kolejno tabele DB2T1 i DB2T2 oraz DB3T1 i DB3T2.
Wykonanie zapytania drugiego zajmowało więcej czasu – najmniej przy wywoływaniu
procedury składowanej za pomocą Data Readera na równi z użyciem Data Readera z komendą
zdefiniowaną w obiekcie SqlCommand jak i wywoływaniem procedury składowanej przez LINQ
TO SQL. Nieco gorzej radził sobie Data Adapter przy użyciu komendy jak i procedury, oraz Entity
Framework. Tak jak przy wykonywaniu pierwszego zapytania czas wykonywania polecenia przez
Data Adapter, oraz jego odchylnie standardowe, mocno wzrosły dla 102400 wierszy w tabeli.
Najgorzej wypadła metoda LINQ TO SQL, której czas wykonywania zapytania ze złączeniami
jest wręcz nieporównywalny z czasami innych metod. Z tego powodu też nie została ona
umieszczona na wykresie. Średnia wykonywania zapytania przez LINQ TO SQL dla 32000 wierszy
wynosi 437,30 sekund a dla 64000 wierszy – 1659,32. Dla takiej ilości wierszy tylko Entity
Framework wykonał zapytanie w czasie większym niż sekunda: te czasy to odpowiednio 1,09 i 2,02
sekundy. Różnice pomiędzy czasami tych dwóch metod wynoszą w przybliżeniu 7 i 27 minut dla
odpowiednio 32000 i 64000 wierszy.
100,00
10,00
DATA_ADAPTER_DATA_SET
DATA_ADAPTER_DATA_SET_SPR
OC
DATA_READER
DATA_READER_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL_SPROC
1,00
0,10
32000
64000
128000
256000
512000
1024000
IIlość wierszy w tabeli DB1Klient
Rysunek 5: Wykres czasów wykonywania drugiego zapytania SELECT dla bazy DB1
Dla bazy DB2 kolejność najbardziej wydajnych metod jest taka sama jak dla bazy DB1, a
wykres prezentuje się bardzo podobnie. Czasy wykonywania są jednak nieco niższe niż w
przypadku bazy DB1. Tak samo jak na poprzednim wykresie pominięte zostały czasy wykonywania
zapytania przez LINQ TO SQL, którego czasy dla 32000 i 64000 wierszy wynoszą 307 i 1154
sekund dla czasów pozostałych metod mieszczących się odpowiednio w granicach 0,2 – 0,8 oraz
1,4 – 1,6.
Czas wykonywania (średnia geometryczna)
100
DATA_ADAPTER_DATA_SE
T
DATA_ADAPTER_DATA_SE
T_SPROC
DATA_READER
DATA_READER_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL_SPROC
10
1
0,1
32000
64000 128000 256000 512000 1024000
Ilość wierszy w tabeli T1
Rysunek 6: Wykres średnich geometrycznych czasów wykonywania zapytania SELECT
typu drugiego dla bazy DB2
Czas w sekundach (średnia geometryczna)
Dla bazy DB3 wraz ze wzrostem ilości wierszy w bazie czas wykonywania zapytania przez
poszczególne metody wzrasta gwałtowniej, odnotowano również wzrost odchylenia standardowego
a różnica między metodami w stosunku do czasów wykonywania zmniejsza się. Najgorzej wypada
Entity Framework. Najlepiej Data Reader oraz procedura składowana wywoływana przez niego
i przez LINQ TO SQL.
100
DATA_ADAPTER_DATA_SET
DATA_ADAPTER_DATA_SET
_SPROC
DATA_READER
DATA_READER_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL_SPROC
10
1
0,1
32000
64000
128000
256000
512000
Ilość wierszy w tabeli T1
Rysunek 7: Wykres średnich geometrycznych czasów wykonywania zapytania SELECT typu
drugiego dla bazy DB3
Zapytanie trzecie
Trzecie zapytanie łączy trzy tabele. Zwraca sumę wartości operacji mnożenia z powiązanych
wierszy. Dla pierwszego schematu zapytanie zwraca imię klienta i sumę wartości jego zamówień
obliczanych jako iloczyn ceny i ilości zakupionych produktów zapisanych w pozycjach zamówień.
Zapytanie trzecie w SQL:
SELECT DB1Klienci.imie, sum(DB1ZamowieniaPozycje.cena * ilosc)
FROM DB1Klienci
JOIN DB1Zamowienia ON DB1Klienci.id_klient=DB1Zamowienia.id_klient
JOIN DB1ZamowieniaPozycje ON DB1Zamowienia.id_zamowienie =
DB1ZamowieniaPozycje.id_zamowienie
GROUP BY DB1Klienci.id_klient, DB1Klienci.imie;
Odpowiadające zapytanie w LINQ:
var klienci = (from k in dc.DB1Kliencis
join z in dc.DB1Zamowienias on k.id_klient equals z.id_klient
join zp in dc.DB1ZamowieniaPozycjes on z.id_zamowienie equals
zp.id_zamowienie
group zp by k.id_klient into g
select
new { klient = g.Select(x => x.DB1Zamowienia.DB1Klienci.imie),
suma = g.Sum(y => y.cena * y.ilosc) } ).ToList();
Dla drugiego i
trzeciego schematu
tabelom
DB1Klienci,
DB1Zamowienia,
DB1ZamowieniaPozycje odpowiadają kolejno tabele DB2T1, DB2T2 i DB2T3 oraz DB3T1,
DB3T2 i DB3T3.
Czasy wykonywania zapytania trzeciego są nieco wyższe, najszybciej wykonywały je procedura
składowana wywoływana przez Data Reader, procedura wywoływana przez LINQ TO SQL i
komenda Data Reader. Niewiele dłużej zapytanie wykonywał Data Adapter przy użyciu komendy
i procedury składowanej. Dla 1024000 wierszy zwiększyły się różnice w czasach wykonywania
zapytania przez Data Adapter i średnia czasu wykonywania przez niego zapytania. Zwiększyła się
również różnica w czasie wykonywania zapytania przy użyciu Entity Framework w stosunku do
szybszych metod, wciąż jednak nie tak drastyczna jak czasy wykonywania zapytania przez LINQ
TO SQL, dla którego dodanie kolejnego złączenia pogłębiło przepaść dzielącą je od pozostałych
metod. Różnice pomiędzy wykonywaniem zapytania przez Entity Framework i LINQ TO SQL dla
32000 i 64000 wierszy wynoszą 20 i 80 minut.
Czas w sekundach (średnia geometryczna)
1000,00
100,00
DATA_ADAPTER_DATA_SET
DATA_ADAPTER_DATA_SET_SPR
OC
DATA_READER
DATA_READER_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL_SPROC
10,00
1,00
0,10
32000
64000
128000
256000
512000 1024000
Ilość wierszy w tabeli DB1Klient
Rysunek 8: Wykres czasów wykonywania trzeciego zapytania SELECT dla bazy DB1
Czas w sekundach (średnia geometryczna)
Wyniki pomiarów dla schematu DB2 są analogiczne w stosunku do wyników dla DB1. Wykres
prezentuje się bardzo podobnie jednak średnie czasy wykonywania zapytania są mniejsze. Podobnie
jak w przypadku DB1 różnica między LINQ TO SQL i pozostałymi metodami jest diametralna.
100
DATA_ADAPTER_DATA_SE
T
DATA_ADAPTER_DATA_SE
T_SPROC
DATA_READER
DATA_READER_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL_SPROC
10
1
0,1
32000
64000 128000 256000 512000 1024000
Ilość wierszy w tabeli DB2T1
R
ysunek 9: Wykres średnich geometrycznych czasów wykonywania zapytania SELECT typu
trzeciego dla bazy DB2
Wyniki wykonania zapytania trzeciego dla DB3 są bardzo podobne do wyników zapytania
drugiego dla tej samej bazy. Podobnie jak w poprzednim przypadku wraz ze wzrostem ilości
wierszy w bazie odnotowano bardziej gwałtowny przyrost czasów wykonywania zapytania dla
Czas w sekundach (średnia geometryczna)
wszystkich metod oraz zmniejszenie różnicy pomiędzy poszczególnymi czasami w stosunku do
czasu wykonywania. Wraz ze wzrostem ilości wierszy rośnie również odchylenie standardowe. Dla
większości przypadków najlepiej wypada metoda Data Reader oraz procedura składowana
wywoływana przez niego i LINQ TO SQL.
1000
100
DATA_ADAPTER_DATA_SE
T
DATA_ADAPTER_DATA_SE
T_SPROC
DATA_READER
DATA_READER_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL_SPROC
10
1
0,1
32000
64000
128000
256000
512000
Ilość wierszy w tabeli DB3T1
Rysunek 10: Wykres średnich geometrycznych czasów wykonywania zapytania SELECT
typu trzeciego dla bazy DB3
Zapytania INSERT
Podczas wykonywania zapytania INSERT do tabeli zapisywane są nowe wiersze z losowo
utworzonymi wartościami. Zapytanie wykonywane jest dla różnej ilości jednorazowo zapisywanych
wierszy. Dla schematu pierwszego dane dodawane są do tabeli DB1Klienci, zapytanie w SQL
wygląda następująco:
INSERT INTO [DB1Klienci]
([mail], [imie], [nazwisko], [miejscowosc], [ulica], [numer_lokalu],[numer_telefonu])
VALUES
(@mail, @imie, @nazwisko, @miejscowosc, @ulica, @numerLokalu, @numerTelefonu);
W schemacie drugim i trzecim dane są dodawane odpowiednio do tabeli DB2T1 oraz DB3T1.
Po wykonaniu zapytania wiersze są usuwane aby następne zapytania były wykonywane na bazie w
identycznym stanie.
Zapytania INSERT wszystkie metody wykonywały w dość zbliżonym czasie, wzrastającym
wraz z ilością dodawanych do bazy wierszy. Czasy wykonywania tworzą dwie grupy metod których
średnie czasy wykonania niewiele się różnią. W grupie szybszej znalazły się metody Command
i wywołanie przez nią procedury składowanej oraz Entity Framework. W grupie wolniejszej
znalazło się LINQ TO SQL i wywoływana przez nią procedura składowana. Różnice w czasach
pomiędzy tymi dwoma grupami wynoszą od 1 do 20 sekund.
Czas w sekundach (średnia geometryczna)
100
10
COMMAND
COMMAND_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
1
0,1
1000
2000
4000
8000
16000
32000
Ilość dodawanych wierszy
Rysunek 11: Wykres czasów wykonywania zapytania INSERT dla 64000 wierszy w tabeli
DB1Klient
Zwiększenie ilości wierszy w bazie danych nie wpłynęło znacząco na czas wykonywania
zapytań INSERT.
Czas w sekundach (średnia geometryczna)
100
10
COMMAND
COMMAND_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
1
0,1
1000
2000
4000
8000
16000
32000
Ilość dodawanych wierszy
Rysunek 12: Wykres czasów wykonywania zapytania INSERT dla 128000 wierszy w tabeli
DB1Klient
Wykres czasów dodawania wierszy do tabeli w bazie DB2 jest bardzo podobny do wykresu dla
bazy DB1. Czasy wykonywania zapytania przez poszczególne metody plasują się takiej samej
kolejności. Średnie czasów są jednak nieco mniejsze.
Czas w sekundach (średnia geometryczna)
100
10
COMMAND
COMMAND_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
1
0,1
1000
2000
4000
8000
16000
32000
Ilość dodawanych wierszy
Rysunek 13: Wykres średnich geometrycznych czasów wykonywania zapytania INSERT
dla 64000 wierszy w tabeli DB2T1
Czas w sekundach (średnia geometryczna)
Podobnie do czasów wykonywania zapytania INSERT dla bazy DB2 prezentują się wyniki dla
bazy DB3. Zwiększyła się różnica między czasami wykonania zapytania przez Command oraz
wywołaną przez niego procedurę i Entity Framework. Czasy wykonania zwiększyły się dla
wszystkich metod. Spadek wydajności procedury wywołanej przez LINQ TO SQL jest związany ze
wzrostem odchylenia standardowego.
100
10
COMMAND
COMMAND_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
1
0,1
1000
2000
4000
8000
16000
32000
Ilość dodanych wierszy
Rysunek 14: Wykres średnich geometrycznych czasów wykonywania zapytania INSERT
dla 64000 wierszy w tabeli DB3T1
Zapytania UPDATE
Podczas wykonywania zapytania UPDATE z tabeli losowo wybrany zostaje wiersz którego
wartości zostaną zaktualizowane przez nowe, otrzymane losowo. Zapytania wykonywane są dla
różnej ilości jednorazowo aktualizowanych wierszy. W pierwszym schemacie aktualizowane są
dane w tabeli DB1Produkty, w języku SQL zapytanie przedstawia się następująco:
UPDATE [DB1Produkty]
SET [cena_jednostkowa] = @cena_jednostkowa
,[nazwa] = @nazwa
,[opis] = @opis
WHERE id_produkt = @id_produkt;
W schemacie drugim i trzecim aktualizowane są odpowiednio tabele DB2T1 i DB3T1.
Wraz ze wzrostem ilości wierszy w bazie, wzrost czasów wykonywania zapytania UPDATE
właściwy dla wzrostu ilości aktualizowanych wierszy nie jest stały dla metod Command,
wywoływania przez Command procedury składowanej oraz wywoływania procedury składowanej
przez LINQ TO SQL. Dla każdej ilości aktualizowanych wierszy metody te zajmują różne pozycje
na liście najlepszych czasów. Jedynie dwie skrajne pozycje są zajmowane przez te same metody w
większości przypadków. Najlepsze okazało się Entity Framework, wzrost czasu wykonywania przez
nie zapytania wraz ze wzrostem ilości aktualizowanych danych jest stabilny. Tak samo jak w
przypadku najgorzej wypadającej metody – LINQ TO SQL. Różnica między najgorszą i najlepszą
metodą wynosi od 2 do 68 sekund.
Czas w sekundach (średnia geometryczna)
100
10
COMMAND
COMMAND_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
1
0,1
1000
2000
4000
8000
16000
32000
Ilość aktualizowanych wierszy
Rysunek 15: Wykres czasów wykonywania zapytania UPDATE dla 64000 wierszy w tabeli
DB1Produkt
Czas w sekundach (średnia geometryczna)
100
10
COMMAND
COMMAND_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
1
0,1
1000
2000
4000
8000
16000
32000
Ilość aktualizowanych wierszy
Rysunek 16: Wykres czasów wykonywania zapytania UPDATE dla 128000 wierszy w tabeli
DB1Produkt
Czas w sekundach (średnia geometryczna)
Czasy wykonywania zapytania UPDATE dla bazy DB2 są większe niż dla bazy DB1, ale
kolejność wyników dla poszczególnych metod jest podobna. Najlepiej wypadają metody Command,
wywoływana przez nią procedura oraz Entity Framework. Najgorzej LINQ TO SQL. Różnice
pomiędzy metodami wynoszą od 3,5 do 44 sekund.
100
10
COMMAND
COMMAND_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
1
0,1
1000
2000
4000
8000
16000
32000
Ilość aktualizowanych wierszy
Rysunek 17: Wykres średnich geometrycznych czasów wykonywania zapytania UPDATE
dla 64000 wierszy w tabeli DB2T1
Czas w sekundach (średnia geometryczna)
Podobnie jak w przypadku dwóch poprzednich schematów zapytanie UPDATE dla bazy DB3
najszybciej wykonują metody Command, wywoływana przez nią procedura składowana oraz Entity
Framework. Najwolniej LINQ TO SQL. Średnie czasy dla wszystkich metod są najwyższe.
1000
100
COMMAND
COMMAND_SPROC
ENTITY_FRAMEWORK
LINQ_TO_SQL
LINQ_TO_SQL_SPROC
10
1
0,1
1000
2000
4000
8000
16000
32000
Ilość aktualizowanych wierszy
Rysunek 18: Wykres średnich geometrycznych czasów wykonywania zapytania UPDATE
dla 64000 wierszy w tabeli DB3T1
Podsumowanie
W większości przeprowadzonych testów metody ORM osiągały gorszą wydajność niż metody
„bezpośrednie”. Najgorzej wypadło LINQ TO SQL, które w wielu sytuacjach znacznie odstawało
od reszty metod. Jego wydajność znacząco pogarszały bardziej skomplikowane zapytania oraz
większa ilość danych. Wywoływanie przez LINQ TO SQL procedur składowanych może stanowić
rozwiązanie problemu bardzo kiepskiej wydajności dla rozbudowanych zapytań. LINQ TO SQL to
proste narzędzie które dobrze sprawdzi się dla niewielkich aplikacji korzystających z małych ilości
danych. Przy użyciu LINQ TO SQL warto używać procedur składowanych.
Entity Framework dla zapytań SELECT i INSERT nie wypadało najlepiej, ale jego wydajność
nie odbiegała znacząco od innych metod. Natomiast dla zapytań UPDATE Entity Framework
znajdował się w czołówce najlepszych metod. Wraz ze wzrostem skomplikowania zapytania oraz
ilości danych jego wydajność nie spadała drastycznie. To narzędzie o sporych możliwościach, które
może konkurować z metodami „bezpośrednimi”.
Data Adapter i Data Set ze względu na możliwość korzystania z danych bez konieczności
utrzymywania połączenia z bazą najlepiej sprawdzi się w aplikacji korzystającej z rzadko
zmieniających się danych. Wydajność tej metody jest gorsza od Data Reader'a, ale w większości
przypadków lepsza od metod ORM.
W większości testów najbardziej wydajną metodą okazał się Data Reader i Command oraz
wywoływanie przez nie procedury składowanej. Nie dostarczają one takich udogodnień jak metody
ORM, ale zapewniają najlepszą wydajność.
References
1. Paul Nielsen, Uttam Parui and Mike White, “Microsoft SQL Server 2008 Bible”, Wiley, 2009
2. Ray Rankins, Paul T. Bertucci, Chris Gallelli and Alex T. Silverstein, “Microsoft SQL Server
2008 R2 Unleashed”, Sams Publishing, 2010