移动端orm框架性能测评

4次阅读

共计 15810 个字符,预计需要花费 40 分钟才能阅读完成。

移动端 orm 框架性能测评

flutter_orm_plugin 发布以来,不少团队试用了,我发现大家对这类数据库相关的库,第一反应就是性能如何,之前确实没做太多行业对比,最近觉得还是有必要做一下性能测试,给大家一个交代的。

在 ios 端,业界比较常用的 orm 框架应该是苹果官方推出的 coredata,还有就是 realm 了。在 android 端 orm 框架我挑了三个比较常用的,greendao,realm 和 activeandroid。我会用 flutter_orm_plugin 跟上面提到的 ios 和 android 端 orm 框架做对比。下面我会分别给出测试用例,测试代码,还有最终数据比较的结果。

测试用例

测试用例我列了以下这些

  • 10000 次插入数据
  • 使用批量接口 10000 次插入数据
  • 10000 次读取数据
  • 10000 次修改数据
  • 使用批量接口 10000 次修改数据
  • 10000 次删除数据
  • 使用批量接口 10000 次删除数据

为什么会有普通插入数据和使用批量接口插入数据的区别,大部分 orm 框架都会对批量操作有一定的优化,所以需要对批量操作进行测试,但是在平时使用,不一定都能用上批量接口(例如多次数据操作不在同一代码块,或者在不同的模块中都要操作数据),所以我们会分别对普通操作和批量操作进行测试。

android 测试代码

首先我们给出 flutter_orm_plugin 的测试代码,由于不想因为 flutter 和原生 channel 通讯产生误差,我们直接用 Luakit 来写 lua 代码做测试(greendao、realm、activeandroid、coredata 都不涉及 flutter 和原生 channel 通讯),flutter_orm_plugin 其实底层就是 luakit 的 orm 框架,这个不影响测试准确性。

循环插入

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 次插入


@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 次插入

@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);

android 测试结果及分析

下面给出测试结果,表格中所有数据的单位是秒,即做 10000 次操作需要的秒数。

  • 可以看到,active android 各项性能都一般。
  • 在使用批量接口的情况下 GreenDao 和 Realm 的性能比较好。
  • 在使用批量接口的情况下 Realm 的性能尤其好,批量插入、查询、批量更改、批量删除都是 Realm 的性能最好,但是 Realm 的非批量接口性能较差,所有可以这样总结,如果代码高内聚,可以把数据操作代码入口都统一使用,Realm 性能是最好的,但这对代码质量、模块设计有要求,当操作数据的代码到处都有,不能使用批量接口时,Realm 的性能是不好的。
  • Luakit 没有提供批量接口,但从图中可以看出,Luakit 的各项性能指标都是比较好的,而且对代码没有要求,即使操作数据的代码不内聚,也不会对性能有影响。

ios 测试代码

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];

ios 测试结果及分析

下面给出测试结果,表格中所有数据的单位是秒,即做 10000 次操作需要的秒数。

  • 可以看到,Coredata 除了批量插入性能是最好的以外,其他项性能都一般。
  • Realm ios 和 Realm android 性能非常相似,批量操作性能优异,但是非批量操作性能一般。可以这样总结,如果代码高内聚,可以把数据操作代码入口都统一使用,Realm 性能是最好的,但这对代码质量、模块设计有要求,当操作数据的代码到处都有,不能使用批量接口时,Realm 的性能是不好的。
  • Luakit 没有提供批量接口,但从图中可以看出,Luakit 的各项性能指标都是比较好的,而且对代码没有要求,即使操作数据的代码不内聚,也不会对性能有影响。
正文完
 0