SQL Server CE 4.0性能比较

Maj*_*cRa 37 sql-server sqlite performance sql-server-ce

SQL Server CE 4(SQL Server Compact Edition 4.0)已不是新闻(如果是,您可以阅读本文)

但是看到SQL Server CE 4与其他数据库的性能比较非常有趣.

特别是:

  • SQLite的
  • SQL Server(1)
  • SQL Server Express*
  • 也许火鸟

(1)适用于功能相当的应用.

不幸的是,谷歌现在提供的主题并没有那么多链接.实际上我找不到任何(适当的SQL CE版本).

如果可以找到或分享这些信息,我们可以在这里收集它以供将来人道使用.

Ale*_*kin 16

在我看来,比较嵌入式数据库(如SQL CE)与服务器端关系数据库(除了SQLite和Firebird的嵌入式版本之外的所有其他数据库)是不正确的.

它们之间的主要区别在于,通用服务器端关系数据库(如MS SQL,MySQL,Firebird Classic和SuperServer等)作为独立服务安装,并在主应用程序范围之外运行.这就是为什么它们可以更好地执行,因为它支持多核和多CPU架构,使用预先缓存,VSS等操作系统功能来增加数据库运行时的吞吐量并且可以占用尽可能多的内存.您的操作系统可以提供单个服务/应用程序.这也意味着它们的性能指标或多或少独立于您的应用程序,但在很大程度上取决于您的硬件.在这方面,我会说任何数据库的服务器版本总是比嵌入式数据库更高的性能.

SQL CE(以及Firebird Embedded,SQLite,TurboSQL和其他一些)是嵌入式数据库引擎,这意味着整个数据库被打包到与您的应用程序一起分发的单个(或最多2个)DLL文件中.由于明显的大小限制(您希望将30 MB DLL与2-3 MB长应用程序一起分发吗?),它们也可以直接在应用程序的上下文中运行,并且数据访问操作的总内存和性能是与应用程序的其他部分共享 - 关于可用内存,CPU时间,磁盘吞吐量等.拥有与数据访问线程并行运行的计算密集型线程可能会导致数据库性能大幅下降.

由于应用程序的不同,这些数据库有不同的选项:server-db提供广泛的用户和权限管理,支持视图和存储过程,而嵌入式数据库通常缺乏对用户和权限管理的支持,并且对视图的支持有限和存储过程(后者失去了在服务器端运行的大部分好处).数据吞吐量是RDBMS的常见瓶颈,服务器版本通常安装在条带化RAID卷上,而嵌入式数据库通常是面向内存的(尝试将所有实际数据保留在内存中)并最小化数据存储访问操作.

因此,有意义的是比较不同的嵌入式RDBMS for .Net的性能,如MS SQL CE 4.0,SQLite,Firebird Embedded,TurboSQL.我不希望在通常的非峰值操作期间出现剧烈的差异,而一些数据库可能会因为与OS的更好集成而为大型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();
        }


    }
}
Run Code Online (Sandbox Code Playgroud)

必要的免责声明:

  1. 我在我的机器上得到了这些结果:Dell Precision WorkStation T7400配备了2个Intel Xeon E5420 CPU和8GB RAM,运行64位Win7 Enterprise.
  2. 我使用连接字符串"Data Source = database_file_name"的两个DB默认设置.
  3. 我使用了SQL CE 4.0和SQLite/System.Data.SQLite的最新版本(从今天起,2011年6月3日).

以下是两个不同样本的结果:

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

因此,正如您所看到的,与SQLCE相比,任何编写操作(创建,更新,删除)在SQLite中需要的时间要多近1000倍.它不一定反映此数据库的一般性能不佳,可能是由于以下原因:

  1. 我用于SQLite的数据提供程序是System.Data.SQLite,它是一个包含托管代码和非托管代码的混合程序集(SQLite最初完全用C语言编写,DLL只提供绑定).可能P/Invoke和数据编组会耗费大量的操作时间.
  2. 默认情况下,SQLCE 4.0很可能会将所有数据缓存在内存中,而每次发生更改时,SQLite都会将大部分数据更改直接刷新到磁盘存储中.可以通过连接字符串为两个数据库提供数百个参数并对其进行适当调整.
  3. 我使用了一系列单个查询来测试数据库.至少SQLCE通过特殊的.Net类支持批量操作,这些类更适合这里.如果SQLite也支持它们(抱歉,我不是这里的专家,我的快速搜索没有产生任何希望),将它们进行比较也会很好.
  4. 我在x64机器上观察到SQLite的许多问题(使用相同的.net适配器):从数据连接意外关闭到数据库文件损坏.我认为数据适配器或库本身存在一些稳定性问题.

  • 将您的sqlite更新代码放入事务中,它将击败sqlce代码。这是一个已知的设计决策,不这样做是不诚实的。 (2认同)

Ale*_*kin 14

以下是关于CodeProject网页基准测试的新鲜出炉文章:

对.Net的嵌入式数据库性能进行基准测试:SQL CE 4.0与SQLite

(该文章现在处于待定状态,您需要登录CodeProject才能访问其内容)

PS:我错误地将我以前的答案标记为社区维基条目,并且不会因此获得任何声誉.这鼓励我为此主题编写Code Project的文章,其中包含一些优化的代码,有关嵌入式dbs的更多其他信息以及结果的统计分析.所以,如果您喜欢这篇文章和我的第二个答案,请将此答案投票.


小智 10

因为我在Alaudo的测试,测试结果以及最终的结论方面遇到了真正的困难,所以我继续前进并在他的程序中玩了一下并提出了修改版本.

它测试以下每个10次并输出平均时间:

  • 没有事务的sqlite,使用默认的jounal_mode
  • 带有事务的sqlite,使用默认的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是我得到的数字:

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
Run Code Online (Sandbox Code Playgroud)

使用sqlite进行200次插入或更新的约3秒可能看起来仍然有点高,但至少比23秒更合理.相反,人们可能会担心SqlCe如何花费太少的时间来完成相同的200个插入或更新,特别是因为在单个事务中使用每个SQL查询或在一个事务中一起使用时,似乎没有真正的速度差异.我不太了解SqlCe来解释这一点,但它让我担心.是否意味着当.Commit()返回时,您不确定更改是否实际写入磁盘?


小智 5

我最近使用SQL CE 4和NHibernate开展了一个项目,我发现性能非常好.使用SQL CE 4,我们能够在一秒钟内插入8000条记录.通过网络上的Oracle,我们每秒只能插入100条记录,甚至使用批量大小和seqhilo方法.

我没有测试它,但是看一下针对.NET的NoSQL产品的一些性能报告,SQL CE 4似乎是独立应用程序的最佳解决方案之一.

只是避免使用Identity列,我们注意到如果不使用它们,性能会提高40倍.当Identity列用作PK时,相同的8000条记录需要40秒才能插入.