Сравнение производительности SQL Server CE 4.0 - PullRequest
37 голосов
/ 09 марта 2011

SQL Server CE 4 (SQL Server Compact Edition 4.0) уже не новость (если это так, вы могли бы прочитать эту статью )

Но очень интересно видеть сравнение производительности SQL Server CE 4 с другими базами данных.

Особенно с:

  • SQLite
  • SQL Server (1)
  • SQL Server Express *
  • возможно, Жар-птица

(1) для приложений, в которых функциональность сопоставима.

К сожалению, сейчас Google не так много ссылок на эту тему. На самом деле я не смог найти ни одного (для правильной версии SQL CE).

Если кто-то может найти или поделиться такой информацией, давайте соберем ее здесь для будущего человечества.

Ответы [ 4 ]

16 голосов
/ 04 июля 2011

На мой взгляд, некорректно сравнивать встроенную базу данных (например, SQL CE) с реляционной базой данных на стороне сервера (как и все остальные, кроме SQLite и встроенной версии Firebird).

Основное различие между ними состоит в том, что реляционные базы данных общего назначения на стороне сервера (такие как MS SQL, MySQL, Firebird Classic и SuperServer и т. Д.) Устанавливаются как независимая служба и запускаются выходит за рамки вашего основного приложения . Вот почему они могут работать намного лучше благодаря встроенной поддержке многоядерных и многопроцессорных архитектур, используя функции ОС, такие как предварительное кэширование, VSS и т. Д., Для увеличения пропускной способности в случае интенсивной работы базы данных и могут требовать столько же памяти, сколько ваша ОС может обеспечить один сервис / приложение. Это также означает, что показатели производительности для них более или менее независимы от вашего приложения, но во многом зависят от вашего оборудования. В этом отношении я бы сказал, что серверные версии любой базы данных всегда более производительны, чем встроенные.

SQL CE (наряду с Firebird Embedded, SQLite, TurboSQL и некоторыми другими) являются встроенными механизмами БД , что означает, что вся база данных упакована в один (или максимально 2) DLL-файл, который распространяется вместе с вашей заявкой. Из-за очевидных ограничений по размеру (хотите ли вы распределить 30 МБ DLL вместе с вашим приложением длиной 2-3 МБ?), Они также запускаются непосредственно в контексте вашего приложения и всего память и производительность для операций доступа к данным совместно используются с другими частями вашего приложения - что касается как доступной памяти, времени ЦП, пропускной способности диска и т. д. Наличие потоков с интенсивными вычислениями, работающих параллельно с вашим потоком доступа к данным, может привести к резкое снижение производительности вашей базы данных.

Из-за различных областей применения эти базы данных имеют разную палитру опций: server-db обеспечивает расширенное управление пользователями и правами, поддержку представлений и хранимых процедур, в то время как встроенная база данных, как правило, не имеет никакой поддержки пользователей и управления правами и имеет ограниченную поддержка представлений и хранимых процедур (последние теряют большинство преимуществ работы на стороне сервера). Пропускная способность данных является обычным узким местом СУБД, версии серверов обычно устанавливаются на чередующиеся тома RAID, тогда как встроенные БД часто ориентированы на память (стараются сохранить все фактические данные в памяти) и сводят к минимуму операции доступа к хранилищу данных.

Итак, возможно, имеет смысл сравнить различные встроенные СУБД для .Net по их производительности, такие как MS SQL CE 4.0, SQLite, Firebird Embedded, TurboSQL . Я не ожидал бы резких различий во время обычной непиковой работы, в то время как некоторые базы данных могут обеспечить лучшую поддержку больших BLOB из-за лучшей интеграции с ОС.

- обновление -

Я должен забрать мои последние слова, потому что моя быстрая реализация показывает очень интересные результаты.

Я написал короткое консольное приложение для тестирования обоих поставщиков данных, вот вам исходный код, если вы хотите поэкспериментировать с ними самостоятельно.

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();
        }


    }
}

Необходимый отказ от ответственности:

  1. Я получил эти результаты на своем компьютере: Dell Precision WorkStation T7400, оснащенный 2 процессорами Intel Xeon E5420 и 8 ГБ оперативной памяти, работающий с 64-разрядной ОС Win7 Enterprise .
  2. Я использовал настройки по умолчанию для обеих БД со строкой соединения "Data Source = database_file_name".
  3. Я использовал последние версии SQL CE 4.0 и SQLite / System.Data.SQLite (с сегодняшнего дня, 3 июня 2011 г.).

Вот результаты для двух разных образцов:

> =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

... и более крупный образец:

=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

Итак, как вы можете видеть, любые операции записи (создание, обновление, удаление) требуют почти в 1000 раз больше времени в SQLite по сравнению с SQLCE. Это не обязательно отражает общую плохую производительность этой базы данных и может быть связано со следующим:

  1. Поставщик данных, который я использую для SQLite, - это System.Data.SQLite , представляющий собой смешанную сборку, содержащую как управляемый, так и неуправляемый код (SQLite изначально написан полностью на C, а DLL обеспечивает только привязки) , Вероятно, P / Invoke и маршалинг данных пожирают хорошую часть рабочего времени.
  2. Скорее всего, SQLCE 4.0 кэширует все данные в памяти по умолчанию, тогда как SQLite сбрасывает большинство изменений данных непосредственно в дисковое хранилище каждый раз, когда происходит изменение. Можно указать сотни параметров для обеих баз данных через строку подключения и настроить их соответствующим образом.
  3. Я использовал серию отдельных запросов для проверки БД. По крайней мере, SQLCE поддерживает массовые операции через специальные классы .Net, которые здесь лучше подходят. Если SQLite также их поддерживает (извините, я здесь не эксперт, и мой быстрый поиск ничего не дал), было бы неплохо сравнить их.
  4. Я наблюдал много проблем с SQLite на машинах x64 (используя тот же адаптер .net): от неожиданного закрытия подключения к данным до повреждения файла базы данных. Я предполагаю, что есть некоторые проблемы со стабильностью либо с адаптером данных, либо с самой библиотекой.
14 голосов
/ 04 июля 2011

Вот моя недавно испеченная статья о сравнительном тестировании на веб-странице CodeProject:

Сравнительный анализ производительности встроенной БД для .Net: SQL CE 4.0 против SQLite

(статья сейчас находится в состоянии ожидания, вам необходимо войти в CodeProject, чтобы получить доступ к ее содержимому)

PS: я ошибочно пометил свой предыдущий ответ как запись вики сообщества и не получил за это никакой репутации,Это побудило меня написать статью для Code Project на эту тему с несколько оптимизированным кодом, дополнительной информацией о встроенных БД и статистическим анализом результатов.Поэтому, пожалуйста, проголосуйте за этот ответ, если вам понравилась статья и мой второй ответ здесь.

10 голосов
/ 07 июля 2011

Поскольку мне очень тяжело с тестами Алаудо, результатами тестов и, в конечном счете, с его заключением, я немного поэкспериментировал с его программой и предложил модифицированную версию.

Он проверяет каждый из следующих 10 раз и выдает среднее время:

  • sqlite без транзакций, по умолчанию jounal_mode
  • sqlite с транзакциями, используя default journal_mode
  • sqlite без транзакций, с использованием WAL jounal_mode
  • sqlite с транзакциями, используя WAL journal_mode
  • sqlce без транзакций
  • sqlce с транзакциями

Вот программа (на самом деле это класс):

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlServerCe;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Linq;

class SqliteAndSqlceSpeedTesting
{
    class Results
    {
        public string test_details;
        public long create_table_time, insert_time, update_time, select_time, delete_time, drop_table_time;
    }

    enum DbType { Sqlite, Sqlce };

    const int NUMBER_OF_TESTS = 200;
    const string create_table_sqlite = "CREATE TABLE Test (id integer not null primary key, textdata nvarchar(500));";
    const string create_table_sqlce = "CREATE TABLE Test (id integer not null identity primary key, textdata nvarchar(500));";
    const string drop_table = "DROP TABLE Test";
    const string insert_data = "INSERT INTO Test (textdata) VALUES ('{0}');";
    const string read_data = "SELECT textdata FROM Test WHERE id = {0}";
    const string update_data = "UPDATE Test SET textdata = '{1}' WHERE id = {0}";
    const string delete_data = "DELETE FROM Test WHERE id = {0}";

    public static void RunTests()
    {
        List<Results> results_list = new List<Results>();

        for (int i = 0; i < 10; i++) {
            results_list.Add(RunTest(DbType.Sqlite, false, false));
            results_list.Add(RunTest(DbType.Sqlite, false, true));
            results_list.Add(RunTest(DbType.Sqlite, true, false));
            results_list.Add(RunTest(DbType.Sqlite, true, true));
            results_list.Add(RunTest(DbType.Sqlce, false));
            results_list.Add(RunTest(DbType.Sqlce, true));                
        }

        foreach (var test_detail in results_list.GroupBy(r => r.test_details)) {
            Console.WriteLine(test_detail.Key);
            Console.WriteLine("Creating table: {0} ms", test_detail.Average(r => r.create_table_time));
            Console.WriteLine("Inserting data: {0} ms", test_detail.Average(r => r.insert_time));
            Console.WriteLine("Updating data: {0} ms", test_detail.Average(r => r.update_time));
            Console.WriteLine("Selecting data: {0} ms", test_detail.Average(r => r.select_time));
            Console.WriteLine("Deleting data: {0} ms", test_detail.Average(r => r.delete_time));
            Console.WriteLine("Dropping table: {0} ms", test_detail.Average(r => r.drop_table_time));
            Console.WriteLine();
        }
    }

    static Results RunTest(DbType db_type, bool use_trx, bool use_wal = false)
    {
        DbConnection conn = null;
        if (db_type == DbType.Sqlite)
            conn = GetConnectionSqlite(use_wal);
        else
            conn = GetConnectionSqlce();

        Results results = new Results();
        results.test_details = string.Format("Testing: {0}, transactions: {1}, WAL: {2}", db_type, use_trx, use_wal);
        results.create_table_time = CreateTable(conn, db_type);
        results.insert_time = InsertTime(conn, use_trx);
        results.update_time = UpdateTime(conn, use_trx);
        results.select_time = SelectTime(conn, use_trx);
        results.delete_time = DeleteTime(conn, use_trx);
        results.drop_table_time = DropTableTime(conn);
        conn.Close();
        return results;
    }

    static DbConnection GetConnectionSqlite(bool use_wal)
    {
        SQLiteConnection conn = new SQLiteConnection("Data Source=sqlite.db");
        if (!File.Exists(conn.Database))
            SQLiteConnection.CreateFile("sqlite.db");
        conn.Open();
        if (use_wal) {
            var command = conn.CreateCommand();
            command.CommandText = "PRAGMA journal_mode=WAL";
            command.ExecuteNonQuery();
        }
        return conn;
    }

    static DbConnection GetConnectionSqlce()
    {
        SqlCeConnection conn = new SqlCeConnection("Data Source=sqlce.sdf");
        if (!File.Exists(conn.Database))
            using (var sqlCeEngine = new SqlCeEngine("Data Source=sqlce.sdf"))
                sqlCeEngine.CreateDatabase();
        conn.Open();
        return conn;
    }

    static long CreateTable(DbConnection con, DbType db_type)
    {
        Stopwatch sw = Stopwatch.StartNew();
        var sqlcmd = con.CreateCommand();
        if (db_type == DbType.Sqlite)
            sqlcmd.CommandText = create_table_sqlite;
        else
            sqlcmd.CommandText = create_table_sqlce;
        sqlcmd.ExecuteNonQuery();
        return sw.ElapsedMilliseconds;
    }

    static long DropTableTime(DbConnection con)
    {
        Stopwatch sw = Stopwatch.StartNew();
        var sqlcmd = con.CreateCommand();
        sqlcmd.CommandText = drop_table;
        sqlcmd.ExecuteNonQuery();
        return sw.ElapsedMilliseconds;
    }

    static long InsertTime(DbConnection con, bool use_trx)
    {
        Stopwatch sw = Stopwatch.StartNew();
        var sqlcmd = con.CreateCommand();
        DbTransaction trx = null;
        if (use_trx) {
            trx = con.BeginTransaction();
            sqlcmd.Transaction = trx;
        }
        for (int i = 0; i < NUMBER_OF_TESTS; i++) {
            sqlcmd.CommandText = string.Format(insert_data, Guid.NewGuid().ToString());
            sqlcmd.ExecuteNonQuery();
        }
        if (trx != null)
            trx.Commit();
        return sw.ElapsedMilliseconds;
    }

    static long SelectTime(DbConnection con, bool use_trx)
    {
        Stopwatch sw = Stopwatch.StartNew();
        var sqlcmd = con.CreateCommand();
        DbTransaction trx = null;
        if (use_trx) {
            trx = con.BeginTransaction();
            sqlcmd.Transaction = trx;
        }
        Random rnd = new Random(DateTime.Now.Millisecond);
        for (var max = NUMBER_OF_TESTS; max-- > 0; ) {
            sqlcmd.CommandText = string.Format(read_data, rnd.Next(1, NUMBER_OF_TESTS - 1));
            sqlcmd.ExecuteNonQuery();
        }
        if (trx != null)
            trx.Commit();
        return sw.ElapsedMilliseconds;
    }

    static long UpdateTime(DbConnection con, bool use_trx)
    {
        Stopwatch sw = Stopwatch.StartNew();
        var sqlcmd = con.CreateCommand();
        DbTransaction trx = null;
        if (use_trx) {
            trx = con.BeginTransaction();
            sqlcmd.Transaction = trx;
        }
        Random rnd = new Random(DateTime.Now.Millisecond);
        for (var max = NUMBER_OF_TESTS; max-- > 0; ) {
            sqlcmd.CommandText = string.Format(update_data, rnd.Next(1, NUMBER_OF_TESTS - 1), Guid.NewGuid().ToString());
            sqlcmd.ExecuteNonQuery();
        }
        if (trx != null)
            trx.Commit();
        return sw.ElapsedMilliseconds;
    }

    static long DeleteTime(DbConnection con, bool use_trx)
    {
        Stopwatch sw = Stopwatch.StartNew();
        Random rnd = new Random(DateTime.Now.Millisecond);
        var order = Enumerable.Range(1, NUMBER_OF_TESTS).ToArray<int>();
        Action<int[], int, int> swap = (arr, a, b) => { int c = arr[a]; arr[a] = arr[b]; arr[b] = c; };
        var sqlcmd = con.CreateCommand();
        DbTransaction trx = null;
        if (use_trx) {
            trx = con.BeginTransaction();
            sqlcmd.Transaction = trx;
        }
        // shuffling the array
        for (var max = NUMBER_OF_TESTS; max-- > 0; ) swap(order, rnd.Next(0, NUMBER_OF_TESTS - 1), rnd.Next(0, NUMBER_OF_TESTS - 1));

        foreach (int index in order) {
            sqlcmd.CommandText = string.Format(delete_data, index);
            sqlcmd.ExecuteNonQuery();
        }
        if (trx != null)
            trx.Commit();
        return sw.ElapsedMilliseconds;
    }
}

Вот числа, которые я получаю:

Testing: Sqlite, transactions: False, WAL: False
Creating table: 24.4 ms
Inserting data: 3084.7 ms
Updating data: 3147.8 ms
Selecting data: 30 ms
Deleting data: 3182.6 ms
Dropping table: 14.5 ms

Testing: Sqlite, transactions: False, WAL: True
Creating table: 2.3 ms
Inserting data: 14 ms
Updating data: 12.2 ms
Selecting data: 6.8 ms
Deleting data: 11.7 ms
Dropping table: 0 ms

Testing: Sqlite, transactions: True, WAL: False
Creating table: 13.5 ms
Inserting data: 20.3 ms
Updating data: 24.5 ms
Selecting data: 7.8 ms
Deleting data: 22.3 ms
Dropping table: 16.7 ms

Testing: Sqlite, transactions: True, WAL: True
Creating table: 3.2 ms
Inserting data: 5.8 ms
Updating data: 4.9 ms
Selecting data: 4.4 ms
Deleting data: 3.8 ms
Dropping table: 0 ms

Testing: Sqlce, transactions: False, WAL: False
Creating table: 2.8 ms
Inserting data: 24.4 ms
Updating data: 42.8 ms
Selecting data: 30.4 ms
Deleting data: 38.3 ms
Dropping table: 3.3 ms

Testing: Sqlce, transactions: True, WAL: False
Creating table: 2.1 ms
Inserting data: 24.6 ms
Updating data: 44.2 ms
Selecting data: 32 ms
Deleting data: 37.8 ms
Dropping table: 3.2 ms

~ 3 секунды для 200 вставок или обновлений с использованием sqlite могут все еще показаться немного высокими, но по крайней мере это более разумно, чем 23 секунды. И наоборот, можно было бы беспокоиться о том, что SqlCe занимает слишком мало времени для выполнения тех же 200 операций вставки или обновления, тем более что, похоже, нет реальной разницы в скорости между наличием каждого SQL-запроса в отдельных транзакциях или в одной транзакции. Я не знаю достаточно о SqlCe, чтобы объяснить это, но меня это беспокоит. Означает ли это, что при возврате .Commit () вы не уверены, что изменения действительно записаны на диск?

5 голосов
/ 18 июля 2012

Недавно я работал над проектом, использующим SQL CE 4 и NHibernate, и обнаружил, что производительность действительно хорошая.С SQL CE 4 мы смогли вставить 8000 записей в секунду.С помощью Oracle по сети мы могли вставлять только 100 записей в секунду, даже при использовании подходов пакетного размера и seqhilo.

Я не тестировал его, но смотрел некоторые отчеты о производительности продуктов NoSQL для .NETSQL CE 4 представляется одним из лучших решений для автономных приложений.

Просто избегайте использования столбцов Identity, мы заметили, что производительность в 40 раз выше, если они не используются.Те же 8000 записей занимали 40 секунд для вставки, когда столбец Identity использовался в качестве PK.

...