flutter_orm_plugin 發佈以來,很多團隊試用了,我發現你們對這類數據庫相關的庫,第一反應就是性能如何,以前確實沒作太多行業對比,最近以爲仍是有必要作一下性能測試,給你們一個交代的。java
在ios端,業界比較經常使用的orm框架應該是蘋果官方推出的coredata,還有就是realm了。在android端orm框架我挑了三個比較經常使用的,greendao,realm和activeandroid。我會用flutter_orm_plugin跟上面提到的ios和android端orm框架作對比。 下面我會分別給出測試用例,測試代碼,還有最終數據比較的結果。android
測試用例我列了如下這些ios
爲何會有普通插入數據和使用批量接口插入數據的區別,大部分orm框架都會對批量操做有必定的優化,因此須要對批量操做進行測試,可是在平時使用,不必定都能用上批量接口(例如屢次數據操做不在同一代碼塊,或者在不一樣的模塊中都要操做數據),因此咱們會分別對普通操做和批量操做進行測試。git
首先咱們給出flutter_orm_plugin 的測試代碼,因爲不想由於flutter和原生channel通信產生偏差,咱們直接用Luakit來寫lua代碼作測試(greendao、realm、activeandroid、coredata都不涉及flutter和原生channel通信),flutter_orm_plugin其實底層就是luakit的orm框架,這個不影響測試準確性。github
Luakit定義orm模型結構並作10000次插入,下面的代碼是ios和android通用的。數據庫
local Student = {
__dbname__ = "test.db",
__tablename__ = "Student",
studentId = {"TextField",{primary_key = true}},
name = {"TextField",{}},
claName = {"TextField",{}},
teacherName = {"TextField",{}},
score = {"RealField",{}},
}
local params = {
name = "Student",
args = Student,
}
Table.addTableInfo(params,function ()
local studentTable = Table("Student」) for i=1,10000 do local s = { studentId = "studentId"..i, name = "name"..i, claName = "claName"..i, teacherName = "teacherName"..i, score = 90, } studentTable(s):save() end end) 複製代碼
activeandroid定義orm模型結構並作10000次插入bash
@Table(name = "Students")
public class Student extends Base {
@Column(name = "studentId")
public String studentId;
@Column(name = "name")
public String name;
@Column(name = "claName")
public String claName;
@Column(name = "teacherName")
public String teacherName;
@Column(name = "score")
public float score;
public Student() {
super();
}
@Override
public String toString() {
return this.studentId;
}
}
for (int i=0 ; i<10000 ;i++) {
ActiveAndroid.beginTransaction();
Student s = new Student();
s.studentId = "studentId"+i;
s.name = "name"+i;
s.teacherName = "teacherName"+i;
s.claName = "claName"+i;
s.score = 90;
s.save();
ActiveAndroid.setTransactionSuccessful();
ActiveAndroid.endTransaction();
}
複製代碼
realm android 定義orm模型結構並作10000次插入框架
public class StudentRealm extends RealmObject {
@PrimaryKey
private String studentId;
@Required
private String name;
@Required
private String teacherName;
@Required
private String claName;
private float score;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClaName() {
return claName;
}
public void setClaName(String ClaName) {
this.claName = claName;
}
public String getTeacherName() {
return teacherName;
}
public void setTeacherName(String teacherName) {
this.teacherName = teacherName;
}
public String getStudentId() {
return studentId;
}
public void setStudentId(String id) {
this.studentId = id;
}
public float getScore() {
return score;
}
public void setScore(float score) {
this.score = score;
}
}
for (int i=0 ; i<10000 ;i++) {
realm.beginTransaction();
StudentRealm realmStudent = realm.createObject(StudentRealm.class,"studentId"+i);
realmStudent.setName("name"+i);
realmStudent.setTeacherName("setTeacherName"+i);
realmStudent.setClaName("setClaName"+i);
realmStudent.setScore(90);
realm.commitTransaction();
}
複製代碼
GreenDao定義orm模型結構並作10000次插入ide
@Entity()
public class Student {
@Id
private String studentId;
@NotNull
private String name;
private String claName;
private String teacherName;
private float score;
@Generated(hash = 1491230551)
public Student(String studentId, @NotNull String name, String claName, String teacherName, float score) {
this.studentId = studentId;
this.name = name;
this.claName = claName;
this.teacherName = teacherName;
this.score = score;
}
@Generated(hash = 1556870573)
public Student() {
}
public String getStudentId() {
return studentId;
}
public void setStudentId(String studentId) {
this.studentId = studentId;
}
@NotNull
public String getName() {
return name;
}
/** Not-null value; ensure this value is available before it is saved to the database. */
public void setName(@NotNull String name) {
this.name = name;
}
public String getClaName() {
return claName;
}
public void setClaName(String claName) {
this.claName = claName;
}
public String getTeacherName() {
return teacherName;
}
public void setTeacherName(String teacherName) {
this.teacherName = teacherName;
}
public float getScore() {
return score;
}
public void setScore(float score) {
this.score = score;
}
}
DaoSession daoSession = ((App) getApplication()).getDaoSession();
StudentDao sd = daoSession.getStudentDao();
for (int i = 0; i < 10000; i++) {
Student s = new Student();
s.setStudentId("StudentId"+i);
s.setClaName("getClaName"+i);
s.setScore(90);
s.setName("name"+i);
s.setTeacherName("tn"+i);
sd.insertOrReplace(s);
}
複製代碼
Luakit沒有提供批量插入接口。性能
active android批量插入10000條數據。
ActiveAndroid.beginTransaction();
for (int i=0 ; i<10000 ;i++) {
Student s = new Student();
s.studentId = "studentId"+i;
s.name = "name"+i;
s.teacherName = "teacherName"+i;
s.claName = "claName"+i;
s.score = 90;
s.save();
}
ActiveAndroid.setTransactionSuccessful();
ActiveAndroid.endTransaction();
複製代碼
realm android批量插入10000條數據。
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
for (int i=0 ; i<10000 ;i++) {
StudentRealm realmStudent = realm.createObject(StudentRealm.class,"studentId"+i);
realmStudent.setName("name"+i);
realmStudent.setTeacherName("setTeacherName"+i);
realmStudent.setClaName("setClaName"+i);
realmStudent.setScore(90);
}
realm.commitTransaction();
複製代碼
GreenDao批量插入10000條數據
DaoSession daoSession = ((App) getApplication()).getDaoSession();
StudentDao sd = daoSession.getStudentDao();
ArrayList<Student> ss = new ArrayList<Student>();
for (int i = 0; i < 10000; i++) {
Student s = new Student();
s.setStudentId("StudentId"+i);
s.setClaName("getClaName"+i);
s.setScore(90);
s.setName("name"+i);
s.setTeacherName("tn"+i);
ss.add(s);
}
sd.insertOrReplaceInTx(ss);
複製代碼
###數據查詢
Luakit作10000次查詢,下面的代碼是ios和android通用的。
local studentTable = Table("Student")
for i=1,10000 do
local result = studentTable.get:where({"studentId"..i},"studentId = ?"):all()
end
複製代碼
active android作10000次查詢。
for (int i=0 ; i<10000 ;i++) {
List<Student> student = new Select()
.from(Student.class)
.where("studentId = ?", "studentId"+i)
.execute();
}
複製代碼
realm android 作10000次查詢。
for (int i=0 ; i<10000 ;i++) {
RealmResults<StudentRealm> students = realm.where(StudentRealm.class).equalTo("studentId", "studentId"+i).findAll();
List<StudentRealm> list = realm.copyFromRealm(students);
}
複製代碼
GreenDao 作10000次查詢
DaoSession daoSession = ((App) getApplication()).getDaoSession();
StudentDao sd = daoSession.getStudentDao();
for (int i = 0; i < 10000; i++) {
List<Student> s = sd.queryBuilder()
.where(StudentDao.Properties.StudentId.eq("StudentId"+i))
.list();
}
複製代碼
###循環更新
Luakit作10000次更新。
local studentTable = Table("Student")
for i=1,10000 do
local result = studentTable.get:where({"studentId"..i},"studentId = ?"):update({name = "name2」}) end 複製代碼
active android作10000次更新。
for (int i=0 ; i<10000 ;i++) {
ActiveAndroid.beginTransaction();
Update update = new Update(Student.class);
update.set("name = ?","name2")
.where("studentId = ?", "studentId"+i)
.execute();
ActiveAndroid.setTransactionSuccessful();
ActiveAndroid.endTransaction();
}
複製代碼
realm android作10000次更新。
for (int i=0 ; i<10000 ;i++) {
realm.beginTransaction();
StudentRealm student = realm.where(StudentRealm.class).equalTo("studentId", "studentId"+i).findFirst();
student.setClaName("ClaName"+(i+1));
realm.copyToRealmOrUpdate(student);
realm.commitTransaction();
}
複製代碼
GreenDao作10000次更新。
for (int i = 0; i < 10000; i++) {
List<Student> s = sd.queryBuilder()
.where(StudentDao.Properties.StudentId.eq("StudentId"+i))
.list();
s.get(0).setName("name2");
sd.update(s.get(0));
}
複製代碼
###批量更新
Luakit沒有批量更新接口。
active android批量更新10000條數據。
ActiveAndroid.beginTransaction();
for (int i=0 ; i<10000 ;i++) {
Update update = new Update(Student.class);
update.set("name = ?","name2")
.where("studentId = ?", "studentId"+i)
.execute();
}
ActiveAndroid.setTransactionSuccessful();
ActiveAndroid.endTransaction();
複製代碼
realm android批量更新10000條數據。
realm.beginTransaction();
for (int i=0 ; i<10000 ;i++) {
StudentRealm student = realm.where(StudentRealm.class).equalTo("studentId", "studentId"+i).findFirst();
student.setClaName("ClaName"+(i+1));
realm.copyToRealmOrUpdate(student);
}
realm.commitTransaction();
複製代碼
GreenDao批量更新10000條數據
ArrayList<Student> ss = new ArrayList<Student>();
for (int i = 0; i < 10000; i++) {
List<Student> s = sd.queryBuilder()
.where(StudentDao.Properties.StudentId.eq("StudentId"+i))
.list();
s.get(0).setName("name2");
ss.add(s.get(0));
}
sd.updateInTx(ss);
複製代碼
###循環刪除
Luakit作10000次刪除操做。
local studentTable = Table("Student")
for i=1,10000 do
studentTable.get:where({"studentId"..i},"studentId = ?"):delete()
end
複製代碼
active android作10000次刪除操做。
for (int i=0 ; i<10000 ;i++) {
ActiveAndroid.beginTransaction();
new Delete().from(Student.class).where("studentId = ?", "studentId"+i).execute();
ActiveAndroid.setTransactionSuccessful();
ActiveAndroid.endTransaction();
}
複製代碼
realm android作10000次刪除操做。
for (int i=0 ; i<10000 ;i++) {
realm.beginTransaction();
StudentRealm student = realm.where(StudentRealm.class).equalTo("studentId", "studentId"+i).findFirst();
student.deleteFromRealm();
realm.commitTransaction();
}
複製代碼
GreenDao作10000次刪除操做。
for (int i = 0; i < 10000; i++) {
List<Student> s = sd.queryBuilder()
.where(StudentDao.Properties.StudentId.eq("StudentId"+i))
.list();
s.get(0).setName("name2");
sd.delete(s.get(0));
}
複製代碼
###批量刪除
Luakit沒有批量刪除接口。
active android批量刪除10000條數據。
ActiveAndroid.beginTransaction();
for (int i=0 ; i<10000 ;i++) {
new Delete().from(Student.class).where("studentId = ?", "studentId"+i).execute();
}
ActiveAndroid.setTransactionSuccessful();
ActiveAndroid.endTransaction();
複製代碼
realm android批量刪除10000條數據。
realm.beginTransaction();
for (int i=0 ; i<10000 ;i++) {
StudentRealm student = realm.where(StudentRealm.class).equalTo("studentId", "studentId"+i).findFirst();
student.deleteFromRealm();
}
realm.commitTransaction();
複製代碼
GreenDao批量刪除10000條數據。
ArrayList<Student> ss = new ArrayList<Student>();
for (int i = 0; i < 10000; i++) {
List<Student> s = sd.queryBuilder()
.where(StudentDao.Properties.StudentId.eq("StudentId"+i))
.list();
ss.add(s.get(0));
}
sd.deleteInTx(ss);
複製代碼
下面給出測試結果,表格中全部數據的單位是秒,即作10000次操做須要的秒數。
能夠看到,active android各項性能都通常。
在使用批量接口的狀況下GreenDao和Realm的性能比較好。
在使用批量接口的狀況下Realm的性能尤爲好,批量插入、查詢、批量更改、批量刪除都是Realm的性能最好,可是Realm的非批量接口性能較差,全部能夠這樣總結,若是代碼高內聚,能夠把數據操做代碼入口都統一使用,Realm性能是最好的,但這對代碼質量、模塊設計有要求,當操做數據的代碼處處都有,不能使用批量接口時,Realm的性能是很差的。
Luakit沒有提供批量接口,但從圖中能夠看出,Luakit的各項性能指標都是比較好的,並且對代碼沒有要求,即便操做數據的代碼不內聚,也不會對性能有影響。
Luakit是跨平臺的,代碼跟android同樣,下面就不列了,只給出Coredata和 Realm ios
Coredata 定義orm模型結構並作10000次插入
@interface Student (CoreDataProperties)
+ (NSFetchRequest<Student *> *)fetchRequest;
@property (nullable, nonatomic, copy) NSString *claName;
@property (nullable, nonatomic, copy) NSString *name;
@property (nonatomic) float score;
@property (nullable, nonatomic, copy) NSString *studentId;
@property (nullable, nonatomic, copy) NSString *teacherName;
@end
self.context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
for (int i=0; i<10000; i++) {
Student *s = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context];
s.studentId = [NSString stringWithFormat:@"studentId%d",i];
s.name = [NSString stringWithFormat:@"name%d",i];
s.teacherName = [NSString stringWithFormat:@"teacherName%d",i];
s.claName = [NSString stringWithFormat:@"claName%d",i];
s.score = 90;
NSError *error = nil;
[self.context save:&error];
}
複製代碼
Realm ios定義orm模型結構並作10000次插入
@interface StudentRLM : RLMObject
@property NSString *studentId;
@property NSString *name;
@property NSString *teacherName;
@property NSString *claName;
@property float score;
@end
for (int i=0; i<10000; i++) {
[realm beginWriteTransaction];
StudentRLM *s = [[StudentRLM alloc] init];
s.studentId = [NSString stringWithFormat:@"studentId%d",i];;
s.name = [NSString stringWithFormat:@"name%d",i];
s.teacherName = [NSString stringWithFormat:@"teacherName%d",i];
s.claName = [NSString stringWithFormat:@"claName%d",i];
s.score = 90;
[realm addOrUpdateObject:s];
[realm commitWriteTransaction];
[realm beginWriteTransaction];
}
複製代碼
Coredata 批量插入10000條數據。
for (int i=0; i<10000; i++) {
Student *s = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context];
s.studentId = [NSString stringWithFormat:@"studentId%d",i];
s.name = [NSString stringWithFormat:@"name%d",i];
s.teacherName = [NSString stringWithFormat:@"teacherName%d",i];
s.claName = [NSString stringWithFormat:@"claName%d",i];
s.score = 90;
}
NSError *error = nil;
[self.context save:&error];
複製代碼
Realm ios批量插入10000條數據。
[realm beginWriteTransaction];
for (int i=0; i<10000; i++) {
StudentRLM *s = [[StudentRLM alloc] init];
s.studentId = [NSString stringWithFormat:@"studentId%d",i];;
s.name = [NSString stringWithFormat:@"name%d",i];
s.teacherName = [NSString stringWithFormat:@"teacherName%d",i];
s.claName = [NSString stringWithFormat:@"claName%d",i];
s.score = 90;
[realm addOrUpdateObject:s];
}
[realm commitWriteTransaction];
[realm beginWriteTransaction];
複製代碼
Coredata 作10000次查詢。
for (int i=0; i<10000; i++) {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
request.predicate = [NSPredicate predicateWithFormat:@"studentId = 'studentId%d'"];
NSArray *objs = [self.context executeFetchRequest:request error:&error];
}
複製代碼
Realm ios作10000次查詢。
for (int i=0; i<10000; i++) {
RLMResults *results = [StudentRLM objectsWhere: [NSString stringWithFormat:@"studentId = 'studentId%d'",i]];
StudentRLM *s = results.firstObject;
}
複製代碼
###循環更新
Coredata 作10000次更新。
for (int i=0; i<10000; i++) {
NSBatchUpdateRequest *batchUpdateRequest = [[NSBatchUpdateRequest alloc] initWithEntityName:@"Student"];
batchUpdateRequest.predicate = [NSPredicate predicateWithFormat:@"studentId = 'studentId%d'"];
batchUpdateRequest.propertiesToUpdate = @{@"name" : @"name2"};
batchUpdateRequest.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *batchResult = [self.context executeRequest:batchUpdateRequest error:&error];
NSError *error = nil;
[self.context save:&error];
}
複製代碼
Realm ios作10000次更新。
for (int i=0; i<10000; i++) {
[realm beginWriteTransaction];
RLMResults *results = [StudentRLM objectsWhere: [NSString stringWithFormat:@"studentId = 'studentId%d'",i]];
NSLog(@"results %lu",(unsigned long)[results count]);
StudentRLM *s = results.firstObject;
[s setName:@"name"];
[realm addOrUpdateObject:s];
[realm commitWriteTransaction];
}
複製代碼
###批量更新
Coredata 批量更新10000條數據。
for (int i=0; i<10000; i++) {
NSBatchUpdateRequest *batchUpdateRequest = [[NSBatchUpdateRequest alloc] initWithEntityName:@"Student"];
batchUpdateRequest.predicate = [NSPredicate predicateWithFormat:@"studentId = 'studentId%d'"];
batchUpdateRequest.propertiesToUpdate = @{@"name" : @"name2"};
batchUpdateRequest.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *batchResult = [self.context executeRequest:batchUpdateRequest error:&error];
}
NSError *error = nil;
[self.context save:&error];
複製代碼
Realm ios批量更新10000條數據。
[realm beginWriteTransaction];
for (int i=0; i<10000; i++) {
RLMResults *results = [StudentRLM objectsWhere: [NSString stringWithFormat:@"studentId = 'studentId%d'",i]];
NSLog(@"results %lu",(unsigned long)[results count]);
StudentRLM *s = results.firstObject;
[s setName:@"name」];
[realm addOrUpdateObject:s];
}
[realm commitWriteTransaction];
複製代碼
###循環刪除
Coredata 作10000次刪除操做。
for (int i=0; i<10000; i++) {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
request.predicate = [NSPredicate predicateWithFormat:@"studentId = 'studentId%d'"];
NSBatchDeleteRequest *batchRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:request];
batchRequest.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *batchResult = [self.context executeRequest:batchRequest error:&error];
NSError *error = nil;
[self.context save:&error];
}
複製代碼
Realm ios作10000次刪除操做。
for (int i=0; i<10000; i++) {
[realm beginWriteTransaction];
RLMResults *results = [StudentRLM objectsWhere: [NSString stringWithFormat:@"studentId = 'studentId%d'",i]];
StudentRLM *s = results.firstObject;
[s setName:@"name"];
[realm deleteObject:s];
[realm commitWriteTransaction];
}
複製代碼
###批量刪除
Coredata 批量刪除10000條數據。
for (int i=0; i<10000; i++) {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
request.predicate = [NSPredicate predicateWithFormat:@"studentId = 'studentId%d'"];
NSBatchDeleteRequest *batchRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:request];
batchRequest.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *batchResult = [self.context executeRequest:batchRequest error:&error];
}
NSError *error = nil;
[self.context save:&error];
複製代碼
Realm ios批量刪除10000條數據。
[realm beginWriteTransaction];
for (int i=0; i<10000; i++) {
RLMResults *results = [StudentRLM objectsWhere: [NSString stringWithFormat:@"studentId = 'studentId%d'",i]];
StudentRLM *s = results.firstObject;
[s setName:@"name"];
[realm deleteObject:s];
}
[realm commitWriteTransaction];
複製代碼
下面給出測試結果,表格中全部數據的單位是秒,即作10000次操做須要的秒數。
能夠看到,Coredata除了批量插入性能是最好的之外,其餘項性能都通常。
Realm ios和Realm android性能很是類似,批量操做性能優異,可是非批量操做性能通常。能夠這樣總結,若是代碼高內聚,能夠把數據操做代碼入口都統一使用,Realm性能是最好的,但這對代碼質量、模塊設計有要求,當操做數據的代碼處處都有,不能使用批量接口時,Realm的性能是很差的。
Luakit沒有提供批量接口,但從圖中能夠看出,Luakit的各項性能指標都是比較好的,並且對代碼沒有要求,即便操做數據的代碼不內聚,也不會對性能有影響。