Moim zdaniem niewłaściwe jest porównywanie wbudowanej bazy danych (takiej jak SQL CE) z relacyjną bazą danych po stronie serwera (jak cała reszta, z wyjątkiem SQLite i wbudowanej wersji Firebird).
Główna różnica między nimi polega na tym, że relacyjne bazy danych ogólnego przeznaczenia po stronie serwera (takie jak MS SQL, MySQL, Firebird Classic i SuperServer itp.) są instalowane jako niezależna usługa i uruchamiane poza zakresem głównej aplikacji . Dlatego mogą działać znacznie lepiej ze względu na nieodłączną obsługę architektur wielordzeniowych i wieloprocesorowych, wykorzystując funkcje systemu operacyjnego, takie jak wstępne buforowanie, VSS itp., Aby zwiększyć przepustowość w przypadku intensywnej pracy bazy danych i mogą zająć tyle pamięci, ile wynosi Twój system operacyjny może zapewnić pojedynczą usługę/aplikację. Oznacza to również, że wskaźniki wydajności dla nich są mniej lub bardziej niezależne od aplikacji, ale w dużej mierze zależą od sprzętu. W związku z tym powiedziałbym, że wersje serwerowe dowolnej bazy danych są zawsze bardziej wydajne w porównaniu z wersjami osadzonymi.
SQL CE (wraz z Firebird Embedded, SQLite, TurboSQL i kilkoma innymi) to osadzone silniki bazy danych , co oznacza, że cała baza danych jest spakowana do jednego (lub maksymalnie 2) plików DLL, które są dystrybuowane razem z aplikacją. Ze względu na oczywiste ograniczenia rozmiaru (czy chciałbyś dystrybuować 30 MB DLL razem z aplikacją o długości 2-3 MB?) one również działają bezpośrednio w kontekście Twojej aplikacji a całkowita pamięć i wydajność operacji dostępu do danych są współdzielone z innymi częściami aplikacji -- dotyczy to zarówno dostępnej pamięci, czasu procesora, przepustowości dysku itp. Posiadanie wątków intensywnie korzystających z obliczeń działających równolegle z wątkiem dostępu do danych może prowadzić do dramatycznego spadku wydajności bazy danych.
Ze względu na różne obszary zastosowania te bazy danych mają różną paletę opcji:serwer-db zapewnia rozbudowane zarządzanie użytkownikami i uprawnieniami, obsługę widoków i procedur składowanych, podczas gdy wbudowana baza danych zwykle nie obsługuje zarządzania użytkownikami i uprawnieniami oraz ma ograniczoną obsługę widoków i procedury składowane (te ostatnie tracą większość korzyści płynących z uruchamiania po stronie serwera). Przepustowość danych jest zwykle wąskim gardłem RDBMS, wersje serwerowe są zwykle instalowane na woluminach RAID rozłożonych, podczas gdy wbudowana baza danych jest często zorientowana na pamięć (spróbuj zachować wszystkie rzeczywiste dane w pamięci) i minimalizuj operacje dostępu do przechowywania danych.
Zatem sensowne byłoby prawdopodobnie porównanie różnych wbudowanych RDBMS dla .Net pod kątem ich wydajności, takich jak MS SQL CE 4.0, SQLite, Firebird Embedded, TurboSQL . Nie spodziewałbym się drastycznych różnic podczas zwykłych operacji poza szczytem, podczas gdy niektóre bazy danych mogą zapewniać lepszą obsługę dużych bloków BLOB dzięki lepszej integracji z systemem operacyjnym.
-- aktualizacja --
Muszę cofnąć ostatnie słowa, ponieważ moja szybka implementacja daje bardzo interesujące wyniki.
Napisałem krótką aplikację konsolową, aby przetestować obu dostawców danych, oto kod źródłowy dla Ciebie, jeśli chcesz sam z nimi poeksperymentować.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using System.Data.SqlServerCe;
using System.Data.Common;
namespace TestSQL
{
class Program
{
const int NUMBER_OF_TESTS = 1000;
private static string create_table;
private static string create_table_sqlce = "CREATE TABLE Test ( id integer not null identity primary key, textdata nvarchar(500));";
private static string create_table_sqlite = "CREATE TABLE Test ( id integer not null primary key, textdata nvarchar(500));";
private static string drop_table = "DROP TABLE Test";
private static string insert_data = "INSERT INTO Test (textdata) VALUES ('{0}');";
private static string read_data = "SELECT textdata FROM Test WHERE id = {0}";
private static string update_data = "UPDATE Test SET textdata = '{1}' WHERE id = {0}";
private static string delete_data = "DELETE FROM Test WHERE id = {0}";
static Action<DbConnection> ACreateTable = (a) => CreateTable(a);
static Action<DbConnection> ATestWrite = (a) => TestWrite(a, NUMBER_OF_TESTS);
static Action<DbConnection> ATestRead = (a) => TestRead(a, NUMBER_OF_TESTS);
static Action<DbConnection> ATestUpdate = (a) => TestUpdate(a, NUMBER_OF_TESTS);
static Action<DbConnection> ATestDelete = (a) => TestDelete(a, NUMBER_OF_TESTS);
static Action<DbConnection> ADropTable = (a) => DropTable(a);
static Func<Action<DbConnection>,DbConnection, TimeSpan> MeasureExecTime = (a,b) => { var start = DateTime.Now; a(b); var finish = DateTime.Now; return finish - start; };
static Action<string, TimeSpan> AMeasureAndOutput = (a, b) => Console.WriteLine(a, b.TotalMilliseconds);
static void Main(string[] args)
{
// opening databases
SQLiteConnection.CreateFile("sqlite.db");
SQLiteConnection sqliteconnect = new SQLiteConnection("Data Source=sqlite.db");
SqlCeConnection sqlceconnect = new SqlCeConnection("Data Source=sqlce.sdf");
sqlceconnect.Open();
sqliteconnect.Open();
Console.WriteLine("=Testing CRUD performance of embedded DBs=");
Console.WriteLine(" => Samplesize: {0}", NUMBER_OF_TESTS);
create_table = create_table_sqlite;
Console.WriteLine("==Testing SQLite==");
DoMeasures(sqliteconnect);
create_table = create_table_sqlce;
Console.WriteLine("==Testing SQL CE 4.0==");
DoMeasures(sqlceconnect);
Console.ReadKey();
}
static void DoMeasures(DbConnection con)
{
AMeasureAndOutput("Creating table: {0} ms", MeasureExecTime(ACreateTable, con));
AMeasureAndOutput("Writing data: {0} ms", MeasureExecTime(ATestWrite, con));
AMeasureAndOutput("Updating data: {0} ms", MeasureExecTime(ATestUpdate, con));
AMeasureAndOutput("Reading data: {0} ms", MeasureExecTime(ATestRead, con));
AMeasureAndOutput("Deleting data: {0} ms", MeasureExecTime(ATestDelete, con));
AMeasureAndOutput("Dropping table: {0} ms", MeasureExecTime(ADropTable, con));
}
static void CreateTable(DbConnection con)
{
var sqlcmd = con.CreateCommand();
sqlcmd.CommandText = create_table;
sqlcmd.ExecuteNonQuery();
}
static void TestWrite(DbConnection con, int num)
{
for (; num-- > 0; )
{
var sqlcmd = con.CreateCommand();
sqlcmd.CommandText = string.Format(insert_data,Guid.NewGuid().ToString());
sqlcmd.ExecuteNonQuery();
}
}
static void TestRead(DbConnection con, int num)
{
Random rnd = new Random(DateTime.Now.Millisecond);
for (var max = num; max-- > 0; )
{
var sqlcmd = con.CreateCommand();
sqlcmd.CommandText = string.Format(read_data, rnd.Next(1,num-1));
sqlcmd.ExecuteNonQuery();
}
}
static void TestUpdate(DbConnection con, int num)
{
Random rnd = new Random(DateTime.Now.Millisecond);
for (var max = num; max-- > 0; )
{
var sqlcmd = con.CreateCommand();
sqlcmd.CommandText = string.Format(update_data, rnd.Next(1, num - 1), Guid.NewGuid().ToString());
sqlcmd.ExecuteNonQuery();
}
}
static void TestDelete(DbConnection con, int num)
{
Random rnd = new Random(DateTime.Now.Millisecond);
var order = Enumerable.Range(1, num).ToArray<int>();
Action<int[], int, int> swap = (arr, a, b) => { int c = arr[a]; arr[a] = arr[b]; arr[b] = c; };
// shuffling the array
for (var max=num; max-- > 0; ) swap(order, rnd.Next(0, num - 1), rnd.Next(0, num - 1));
foreach(int index in order)
{
var sqlcmd = con.CreateCommand();
sqlcmd.CommandText = string.Format(delete_data, index);
sqlcmd.ExecuteNonQuery();
}
}
static void DropTable(DbConnection con)
{
var sqlcmd = con.CreateCommand();
sqlcmd.CommandText = drop_table;
sqlcmd.ExecuteNonQuery();
}
}
}
Niezbędne zastrzeżenie:
- Mam następujące wyniki na moim komputerze:Dell Precision WorkStation T7400 wyposażona w 2 procesory Intel Xeon E5420 i 8 GB pamięci RAM, z 64-bitowym systemem Win7 Enterprise .
- Użyłem domyślnych ustawień dla obu baz danych z ciągiem połączenia „Źródło danych=database_file_name”.
- Używałem najnowszych wersji zarówno SQL CE 4.0, jak i SQLite/System.Data.SQLite (od dzisiaj, 3 czerwca 2011).
Oto wyniki dla dwóch różnych próbek:
> =Testing CRUD performance of embedded DBs= > => Samplesize: 200 > ==Testing SQLite== > Creating table: 396.0396 ms > Writing data: 22189.2187 ms > Updating data: 23591.3589 ms > Reading data: 21.0021 ms > Deleting data: 20963.0961 ms > Dropping table: 85.0085 ms > ==Testing SQL CE 4.0== > Creating table: 16.0016 ms > Writing data: 25.0025 ms > Updating data: 56.0056 ms > Reading data: 28.0028 ms > Deleting data: 53.0053 ms > Dropping table: 11.0011 ms
... i większa próbka:
=Testing CRUD performance of embedded DBs= => Samplesize: 1000 ==Testing SQLite== Creating table: 93.0093 ms Writing data: 116632.6621 ms Updating data: 104967.4957 ms Reading data: 134.0134 ms Deleting data: 107666.7656 ms Dropping table: 83.0083 ms ==Testing SQL CE 4.0== Creating table: 16.0016 ms Writing data: 128.0128 ms Updating data: 307.0307 ms Reading data: 164.0164 ms Deleting data: 306.0306 ms Dropping table: 13.0013 ms
Tak więc, jak widać, wszelkie operacje pisania (tworzenie, aktualizacja, usuwanie) wymagają prawie 1000 razy więcej czasu w SQLite w porównaniu do SQLCE. Niekoniecznie odzwierciedla to ogólną złą wydajność tej bazy danych i może wynikać z następujących przyczyn:
- Dostawcą danych, którego używam dla SQLite jest System.Data.SQLite , czyli mieszany zestaw zawierający zarówno kod zarządzany, jak i niezarządzany (SQLite jest pierwotnie napisany w całości w języku C, a biblioteka DLL zapewnia tylko powiązania). Prawdopodobnie P/Invoke i marshaling danych pochłaniają sporą część czasu działania.
- Najprawdopodobniej SQLCE 4.0 domyślnie buforuje wszystkie dane w pamięci, podczas gdy SQLite opróżnia większość zmian danych bezpośrednio do pamięci dyskowej za każdym razem, gdy następuje zmiana. Można dostarczyć setki parametrów dla obu baz danych za pomocą ciągu połączenia i odpowiednio je dostroić.
- Użyłem serii pojedynczych zapytań do przetestowania bazy danych. Przynajmniej SQLCE obsługuje operacje zbiorcze za pośrednictwem specjalnych klas .Net, które lepiej pasowałyby tutaj. Jeśli SQLite również je obsługuje (przepraszam, nie jestem tutaj ekspertem, a moje szybkie wyszukiwanie nie dało nic obiecującego), byłoby miło je również porównać.
- Zaobserwowałem wiele problemów z SQLite na maszynach x64 (przy użyciu tego samego adaptera .net):od nieoczekiwanego zamknięcia połączenia danych do uszkodzenia pliku bazy danych. Przypuszczam, że występują pewne problemy ze stabilnością albo z adapterem danych, albo z samą biblioteką.