移动端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;@endself.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;@endfor (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的各项性能指标都是比较好的,而且对代码没有要求,即使操作数据的代码不内聚,也不会对性能有影响。