測試 ClownFish、CYQ、Entity Framework、Moon、MySoft、NHibernate、PDF、XCode資料訪問元件效能
由於很多園友反饋,有的元件不應該缺席、測試複雜度不夠、測試還缺乏一定的公平。
因此考慮在下一個版本中,確保在更加公平的前提下進行更高複雜度的測試 。
同時將分為2組測試,純SQL元件及純ORM元件, 如果純SQL元件不足,就只進行純ORM元件的測試。
待加入測試元件有Dapper、PetaPoco/NPoco、Elinq、FluentData ,有更好的建議,請留言。
--------------------------------------------------------------
“啊!你在用ORM?會不會效能很差啊?”
用數字來說話,打破模糊的、傳言的印象。標題提到的元件“增刪改查”都實現了測試程式碼,所以除了測試外,也可以把此專案作為各個元件的入門參考demo。
專案使用的是.Net Framework 4.0可以使用2010或2012開啟。
預設測試資料庫使用SqlServer
測試前,請先建立資料表Test(使用名為Test的資料庫,如果不用這個資料庫,請更改資料庫連線字串)
CREATE TABLE [dbo].[Test](
[RowId] [int] IDENTITY(1,1) NOT NULL,
[Guid] [varchar](50) primary key NOT NULL,
[Content] [nvarchar](500) NULL,
[CreateDate] [
[EditDate] [datetime] NULL
)
測試前清表:truncatetable Test
預設連線字串是:Data Source=.;Initial Catalog=Test;Integrated Security=True
此程度測試程式碼使用了介面規範,並沒有為了省事耦合在程式裡,因此編寫修改測試程式碼非常簡單,所以也希望各位園友自己寫上一段測試測試。
由於這些元件基本都是第一次使用,所以可能導致部分元件測試程式碼編寫並不合理,敬請指點。
注意:各元件的測試均採用一樣的測試思路,也即ORM對ORM,SQL對SQL,迴圈也是一樣的迴圈。
不必拘泥於15和23的區別,而是要區別10和100的區別,也就是在一個數量級別之間比較。
執行緒我分別寫了Task、ThreadPool和Thread的執行方式,根據自己的喜歡選擇。
Task可以很出色的取消建立的執行緒,ThreadPool測試會較為穩定,Thread很容易導致SQL連線池爆破。 測試之前,可先預熱一下——各個元件進行一定數量查詢。
先看兩張測試圖。
分別是ClownFish和EF的測試圖,X軸是執行緒名稱,Y軸是耗時。
特別說明: CYQ的資料是使用更新前的元件,在昨晚測試整理的。今天收到秋天園友的反饋,已經在git提交了更新,測試的資料也迴歸正常。
因此,資料也調整過來。(2013-07-27 13:53)
關於XCode的測試資料,請參看大石頭的說明。大石頭建議在大資料的訪問時使用此元件,並開啟快取。
同時不再接受新的元件更新,除非是在新的測試版本中,以避免針對性的更新。
資料格式:平均值-最高值-最低值
測試順序:增、改、刪
100“執行緒” 查詢10次 增刪改
ClownFish | Moon | | |
增 | 23-96-2 | 21-76-6 | 8-25-4 |
刪 | 16-49-2 | 15-37-5 | 4-18-2 |
改 | 46-116-3 | 23-56-3 | 7-33-3 |
CYQOrm | EFOrm | MoonOrm | MySoftOrm | NHibernateOrm | PDFOrm | XCodeOrm | |
增 | 24-79-5 | 23-71-8 | 10-64-3 | 46-102-24 | 21-47-7 | 12-31-4 | 15-60-9 |
刪 | 11-61-4 | 61-137-17 | 10-29-3 | 41-267-15 | 41-103-9 | 20-54-3 | 340-623-173 |
改 | 29-182-18 | 37-113-15 | 5-15-2 | 110-195-52 | 37-128-7 | 17-50-3 | 364-476-246 |
1000“執行緒” 查詢10次增刪改
ClownFish | Moon | | |
增 | 9-276-2 | 13-49-2 | 7-44-3 |
刪 | 22-125-2 | 7-43-3 | 9-41-3 |
改 | 20-299-2 | 10-123-3 | 8-66-2 |
CYQOrm | EFOrm | MoonOrm | MySoftOrm | NHibernateOrm | PDFOrm | XCodeOrm | |
增 | 79-961-3 | 29-306-9 | 6-52-3 | 50-386-11 | 12-259-4 | 10-48-3 | 14-231-8 |
刪 | 29-471-4 | 43-321-11 | 14-95-2 | 78-386-13 | 45-237-7 | 22-90-4 | 362-729-177 |
改 | 30-438-3 | 59-334-12 | 27-215-14 | 106-647-18 | 42-294-4 | 17-128-3 | 410-755-199 |
查詢測試,資料行100W
100“執行緒” 查詢返回Top 100
使用沒有索引的列RowId排序
ClownFish | Moon | | |
查 | 2-19-1 | 1-9-0 | 1-47-0 |
查詢採用的根據元件提供的分頁或者Top查詢功能。(moon及xcode使用的是分頁查詢)
使用沒有索引的列RowId排序
CYQOrm | EFOrm | MoonOrm | MySoftOrm | NHibernateOrm | PDFOrm | XCodeOrm | |
查 | 1339-2220-281 | 1452-3668-735 | 5230-8032-3241 | 1287-1670-240 | 3372-6264-954 | 2629-3825-836 | 1696-5363-716 |
使用有索引的列Guid排序
CYQOrm | EFOrm | MoonOrm | MySoftOrm | NHibernateOrm | PDFOrm | XCodeOrm | |
查 | 16-32-13 | 5-8-3 | 5967-14644-1639 | 2-5-1 | 7-127-2 | 1-17-0 | 1-9-0 |
經過測試,發現執行緒越多,越容易出現問題“超時時間已到,但是尚未從池中獲取連線。出現這種情況可能是因為所有池連線均在使用,並且達到了最大池大小。”導致訪問很不穩定。
一個好的資料訪問層應該是可以優雅的接受並處理大併發的訪問,而不應該僅僅只盯住表面上的測試資料(除非有數量級上的差距)。
整個程式框架怎麼才設計出色,更加優雅的面對請求,才是應該花更多心思去考慮的。
從上面也可以看到,ORM效能其實並沒有一般人想象的那麼糟糕。
最後看看程式的程式碼是怎麼樣的。
專案目錄
測試程式碼介面
public interface ITest{
bool Insert();
bool Update(string guid, string content);
DataTable Select(int count);
List<string> GetGuidList(int count);
bool Delete(string guid);
} View Code
ClownFish-SQL測試程式碼
public class ClownFishTest : DbContextHolderBase, ITest{
static ClownFishTest()
{
DbContext.RegisterDbConnectionInfo("sqlserver", "System.Data.SqlClient", "@", Control.ConnectionStrings);
}
public void TruncateTable()
{
DbHelper.ExecuteNonQuery(SqlString.TruncateTable, null, DbContext, CommandKind.SqlTextNoParams);
}
public bool Insert()
{
var parameter = new { Guid = Guid.NewGuid(), Content = string.Empty };
return (DbHelper.ExecuteNonQuery(SqlString.Insert, parameter, DbContext, CommandKind.SqlTextWithParams) > 0);
}
public bool Update(string guid, string content)
{
var parameter = new { Guid = guid, Content = content };
return (DbHelper.ExecuteNonQuery(SqlString.Update, parameter, DbContext, CommandKind.SqlTextWithParams) > 0);
}
public DataTable Select(int count)
{
return DbHelper.FillDataTable(string.Format(SqlString.Select, count), null, DbContext, CommandKind.SqlTextNoParams);
}
public List<string> GetGuidList(int count)
{
List<string> result = new List<string>();
DataTable dtb = Select(count);
if (dtb == null || dtb.Rows.Count == 0)
return result;
result.AddRange(from DataRow row in dtb.Rows select row["Guid"].ToString());
return result;
}
public bool Delete(string guid)
{
var parameter = new { Guid = guid };
return (DbHelper.ExecuteNonQuery(SqlString.Delete, parameter, DbContext, CommandKind.SqlTextWithParams) > 0);
}
} View Code Moon-SQL測試程式碼 public class MoonTest : ITest
{
private readonly DB _dbHelper = DBFactory.DefaultDB;
public bool Insert()
{
return (_dbHelper.ExecuteOneSql(string.Format(SqlString.InsertFormat, Guid.NewGuid(), string.Empty)) > 0);
}
public bool Update(string guid, string content)
{
return (_dbHelper.ExecuteOneSql(string.Format(SqlString.UpdateFormat, guid, content)) > 0);
}
public DataTable Select(int count)
{
return _dbHelper.GetDataTable(string.Format(SqlString.Select, count));
}
public List<string> GetGuidList(int count)
{
List<string> result = new List<string>();
DataTable dtb = Select(count);
if (dtb == null || dtb.Rows.Count == 0)
return result;
result.AddRange(from DataRow row in dtb.Rows select row["Guid"].ToString());
return result;
}
public bool Delete(string guid)
{
return (_dbHelper.ExecuteOneSql(string.Format( SqlString.DeleteFormat,guid)) > 0);
}
} View Code
PDF-SQL測試程式碼
{
private readonly AdoHelper _dbHelper = MyDB.GetDBHelperByConnectionName("pdf");
public bool Insert()
{
IDataParameter[] parameters =
{
new SqlParameter("@Guid",SqlDbType.VarChar,50){Value=Guid.NewGuid().ToString()},
new SqlParameter("@Content",SqlDbType.NVarChar,500){Value=string.Empty}
};
return (_dbHelper.ExecuteNonQuery(SqlString.Insert, CommandType.Text, parameters) > 0);
}
public bool Update(string guid, string content)
{
IDataParameter[] parameters =
{
new SqlParameter("@Guid",SqlDbType.VarChar,50){Value=guid},
new SqlParameter("@Content",SqlDbType.NVarChar,500){Value=content}
};
return (_dbHelper.ExecuteNonQuery(SqlString.Update, CommandType.Text, parameters) > 0);
}
public DataTable Select(int count)
{
return _dbHelper.ExecuteDataSet(string.Format(SqlString.Select, count)).Tables[0];
}
public List<string> GetGuidList(int count)
{
List<string> result = new List<string>();
DataTable dtb = Select(count);
if (dtb == null || dtb.Rows.Count == 0)
return result;
result.AddRange(from DataRow row in dtb.Rows select row["Guid"].ToString());
return result;
}
public bool Delete(string guid)
{
IDataParameter[] parameters =
{
new SqlParameter("@Guid",SqlDbType.VarChar,50){Value=guid}
};
return (_dbHelper.ExecuteNonQuery(SqlString.Delete, CommandType.Text, parameters) > 0);
}
} View Code
CYQ-ORM測試程式碼
public class CyqOrmTest : ITest{
public bool Insert()
{
bool result;
using (MAction actiont = new MAction(TableNames.Test))
{
actiont.Set("Guid", Guid.NewGuid());
result = actiont.Insert(InsertOp.None);
}
return result;
}
public bool Update(string guid, string content)
{
bool result;
using (MAction actiont = new MAction(TableNames.Test))
{
actiont.Set("Content", content);
result = actiont.Update("Guid='" + guid + "'");
}
return result;
}
public DataTable Select(int count)
{
DataTable result;
using (MAction actiont = new MAction(TableNames.Test))
{
result = actiont.Select(count, "order by Guid").ToDataTable();
//actiont.Select(count, "order by RowId"); }
return result;
}
public List<string> GetGuidList(int count)
{
List<string> result = new List<string>();
DataTable dtb = Select(count);
if (dtb == null || dtb.Rows.Count == 0)
return result;
result.AddRange(from DataRow row in dtb.Rows select row["Guid"].ToString());
return result;
}
public bool Delete(string guid)
{