SQLite3在swift3.0性能测试

https://github.com/targetcloud/SQLite3DemoSwift3

结论:用BATCH性能最好(事务+预处理),swift3.0的写法有许多地方需要注意,本文也包括了SQLite3基础的CRUD多种写法供读者参考

1、在工具类中批量插入时发现一个BUG,即Float与Double都需要判断,不然会插入空值

    fileprivate func insertBind(_ stmt: OpaquePointer, values: [Any]) -> Bool  {
        var index: Int32 = 1
        for obj in values{
            if obj is Int{
                sqlite3_bind_int(stmt, index, Int32(obj as! Int))
            } else if obj is Double{
                sqlite3_bind_double(stmt, index, obj as! Double)
            } else if obj is Float{
                sqlite3_bind_double(stmt, index, Double(obj as! Float))//MARK:- 重点!Float要判断
            }else if obj is String{
                sqlite3_bind_text(stmt, index, obj as! String, -1, SQLITE_TRANSIENT)
            }else {
                continue
            }
            index += 1
        }
        return sqlite3_step(stmt) == SQLITE_DONE
    }

2、取 SQLITE_TEXT时的写法在swift3中如下

                    let value =  String(cString:sqlite3_column_text(stmt, i)!)//MARK:-最新写法
                    let valueStr = String(cString: value, encoding: String.Encoding.utf8)

    func queryAllStmt(_ sql :String) ->[[String:Any]] {
        var stmt: OpaquePointer? = nil
        var queryDataArrM = [[String:Any]]()
        if sqlite3_prepare_v2(appdb, sql, -1, &stmt, nil) != SQLITE_OK {
            print("prepare fail")
            return queryDataArrM
        }
        while sqlite3_step(stmt) == SQLITE_ROW {
            let count = sqlite3_column_count(stmt)
            var dict = [String:Any]()
            for i in 0..<count {
                let columnName = String(cString: sqlite3_column_name(stmt, i), encoding: String.Encoding.utf8)
                let type = sqlite3_column_type(stmt, i)
                if type == SQLITE_INTEGER {
                    let value = sqlite3_column_int(stmt, i)
                    dict[columnName!] = value
                    print(columnName,value)
                }
                if type == SQLITE_FLOAT {
                    let value = sqlite3_column_double(stmt, i)
                    dict[columnName!] = value
                    print(columnName,value)
                }
                if type == SQLITE_TEXT {
                    let value =  String(cString:sqlite3_column_text(stmt, i)!)//MARK:-最新写法
                    let valueStr = String(cString: value, encoding: String.Encoding.utf8)
                    dict[columnName!] = valueStr
                    print(columnName,valueStr)
                }
            }
            queryDataArrM.append(dict)
        }
        sqlite3_finalize(stmt)
        return queryDataArrM
    }

3、批量INSERT时传入的是二维数组,定义与调用写法如下

定义

SQLite3Tool.swift

    //批量INSERT
    func insertBatch(_ tableName: String, columnNameArray: [String], valuesBlock: () -> Array<Array<Any>>){
        guard let stmt: OpaquePointer = getPrepareStmt(tableName, columnNameArray: columnNameArray) else { return}
        beginTransaction()
        for array in valuesBlock(){
            insertBind(stmt, values: array)
            resetStmt(stmt)
        }
        commitTransaction()
        releaseStmt(stmt)
    }

调用

User.swift

    class func insertBatch(_ valuesBlock: @escaping () -> (Array<Array<Any>>)){
        SQLite3Tool.shareInstance.insertBatch("t_user", columnNameArray: ["name", "score","age"], valuesBlock: { () -> Array<Array<Any>> in
            return valuesBlock()
        })
    }

调用

ViewController.swift

    //时间测试4
    @IBAction func timeTest4(){
        let beginTime = CFAbsoluteTimeGetCurrent()
        let user = User(name: "时间测试4", age: 44, score: 44.43)
        User.insertBatch({() -> (Array<Array<Any>>) in
            var array = [[Any]]()//var array = Array<Array<Any>>()  //MARK:- 重点!性能最好
            for _ in 0...9999{
                array.append([user.name, user.score,user.age])
            }
            return array
        })
        let endTime = CFAbsoluteTimeGetCurrent()
        timeLabel.text = "开事务+Batch\(endTime - beginTime)"
    }


注意点:定义与调用的地方相互之间列名数组与值数组顺序要对应,可以根据传入的列名动态拼接SQL的,不需要写完整的SQL


附完整版的工具类

//
//  SQLite3Tool.swift
//  SQLite3Demo
//
//  Created by targetcloud on 2016/12/7.
//  Copyright © 2016年 targetcloud. All rights reserved.
//

import Foundation

class SQLite3Tool: NSObject {
    static let shareInstance = SQLite3Tool()
    var appdb: OpaquePointer? = nil
    let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
    
    override init() {
        super.init()
        //        let path = "/Users/targetcloud/Desktop/appdb.sqlite"//实际中用下面的path
        let docDir: String! = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
        //        let path = docDir + "/appdb.sqlite"//建议下面写法
        //        print(path)
        let DBPath = (docDir! as NSString).appendingPathComponent("appdb.sqlite")
        let path = DBPath.cString(using: String.Encoding.utf8)
        print(DBPath)
        /*
         /Users/targetcloud/Library/Developer/CoreSimulator/Devices/A2BB310A-7C11-4A30-B1DE-B3488B7D87D7/data/Containers/Data/Application/0E8D813A-BD6D-4E4F-B500-27C3B5E896DA/Documents/appdb.sqlite
        */
        if  sqlite3_open(path, &appdb) == SQLITE_OK {
            dropTable("t_user")
            createTable("create table if not exists t_user(id integer primary key autoincrement, name text not null, age integer, score real default 60, money real default 100 )")
        }else {
            print("open fail")
        }
    }
    
    func queryAllStmt(_ sql :String) ->[[String:Any]] {
        var stmt: OpaquePointer? = nil
        var queryDataArrM = [[String:Any]]()
        if sqlite3_prepare_v2(appdb, sql, -1, &stmt, nil) != SQLITE_OK {
            print("prepare fail")
            return queryDataArrM
        }
        while sqlite3_step(stmt) == SQLITE_ROW {
            let count = sqlite3_column_count(stmt)
            var dict = [String:Any]()
            for i in 0..<count {
                let columnName = String(cString: sqlite3_column_name(stmt, i), encoding: String.Encoding.utf8)
                let type = sqlite3_column_type(stmt, i)
                if type == SQLITE_INTEGER {
                    let value = sqlite3_column_int(stmt, i)
                    dict[columnName!] = value
                    print(columnName,value)
                }
                if type == SQLITE_FLOAT {
                    let value = sqlite3_column_double(stmt, i)
                    dict[columnName!] = value
                    print(columnName,value)
                }
                if type == SQLITE_TEXT {
                    let value =  String(cString:sqlite3_column_text(stmt, i)!)//MARK:-最新写法
                    let valueStr = String(cString: value, encoding: String.Encoding.utf8)
                    dict[columnName!] = valueStr
                    print(columnName,valueStr)
                }
            }
            queryDataArrM.append(dict)
        }
        sqlite3_finalize(stmt)
        return queryDataArrM
    }
    
    func queryAll(_ sql :String) {
        //参数: 一个打开的数据库、需要执行的SQL语句、查询结果回调(执行0次或多次)、回调函数的第一个值、错误信息
        let result = sqlite3_exec(appdb, sql, { (
            firstValue, columnCount, values , columnNames ) -> Int32 in // 参数: 外层的第4个参数的值、列的个数、结果值的数组所有列的名称数组。返回值: 0代表继续执行一直到结束, 1代表执行一次
            let count = Int(columnCount)
            for i in 0..<count {
                let column = columnNames?[i]
                let columnNameStr = String(cString: column!, encoding: String.Encoding.utf8)//(columnNames?[i]!)!
                let value = values?[i]
                let valueStr = String(cString: value!, encoding: String.Encoding.utf8)//(values?[i]!)!
                print(columnNameStr! + "=" + valueStr!)
            }
            return 0//0代表继续执行一直到结束, 1代表执行一次
        }, nil, nil)
        if result == SQLITE_OK {
            print("all query ok")
        }else {
            print("all query fail")
        }
    }
    
    //建表
    fileprivate func createTable(_ sql: String) ->Bool {
        return execSQL(sql)
    }
    
    //删表
    fileprivate func dropTable(_ tableName: String) ->Bool {
        let sql = "drop table if exists "+tableName
        return execSQL(sql)
    }
    
    //普通更新
    func updateRecord(_ table: String, setStr: String, condition: String?) -> Bool {
        let whereCondition = (condition == nil) ? ("") : ("where \(condition!)")
        let sql = "update \(table) set \(setStr) \(whereCondition)"
        return execSQL(sql)
    }
    
    //普通INSERT
    func insert(_ tableName: String, columnNameArray: [String], values: CVarArg...) -> Bool {
        return insert(tableName, columnNameArray: columnNameArray, valueArray:values)
    }
    
    //普通INSERT
    func insert(_ tableName: String, columnNameArray: [String], valueArray: [Any]) -> Bool {
        if columnNameArray.count != valueArray.count{
            return false
        }
        let tempColumnNameArray = columnNameArray as NSArray
        let columnNames = tempColumnNameArray.componentsJoined(by: ",")
        let tempValueArray = valueArray as NSArray
        let values = tempValueArray.componentsJoined(by: "\',\'")
        let sql = "INSERT INTO \(tableName)(\(columnNames)) values (\'\(values)\')"
        return execSQL(sql)
    }
    
    //批量INSERT
    func insertBatch(_ tableName: String, columnNameArray: [String], valuesBlock: () -> Array<Array<Any>>){
        guard let stmt: OpaquePointer = getPrepareStmt(tableName, columnNameArray: columnNameArray) else { return}
        beginTransaction()
        for array in valuesBlock(){
            insertBind(stmt, values: array)
            resetStmt(stmt)
        }
        commitTransaction()
        releaseStmt(stmt)
    }
    
    //step Stmt
    fileprivate func insertBind(_ stmt: OpaquePointer, values: [Any]) -> Bool  {
        var index: Int32 = 1
        for obj in values{
            if obj is Int{
                sqlite3_bind_int(stmt, index, Int32(obj as! Int))
            } else if obj is Double{
                sqlite3_bind_double(stmt, index, obj as! Double)
            } else if obj is Float{
                sqlite3_bind_double(stmt, index, Double(obj as! Float))//MARK:- 重点!Float要分开判断
            }else if obj is String{
                sqlite3_bind_text(stmt, index, obj as! String, -1, SQLITE_TRANSIENT)
            }else {
                continue
            }
            index += 1
        }
        return sqlite3_step(stmt) == SQLITE_DONE
    }
    
    //得到INSERT Stmt
    fileprivate func getPrepareStmt(_ tableName: String, columnNameArray: [String]) -> OpaquePointer? {
        let tempColumnNameArray = columnNameArray as NSArray
        let columnNames = tempColumnNameArray.componentsJoined(by: ",")
        var tempValues: Array = [String]()
        for _ in 0 ..< columnNameArray.count{
            tempValues.append("?")
        }
        let valuesStr = (tempValues as NSArray).componentsJoined(by: ",")
        let prepareSql = "INSERT INTO \(tableName)(\(columnNames)) values (\(valuesStr))"
        var stmt: OpaquePointer? = nil
        if sqlite3_prepare_v2(appdb, prepareSql, -1, &stmt, nil) != SQLITE_OK{
            print("prepare fail")
            sqlite3_finalize(stmt)
            return nil
        }
        return stmt!
    }
    
    //重置Stmt
    fileprivate func resetStmt(_ stmt: OpaquePointer) -> Bool {
        return sqlite3_reset(stmt) == SQLITE_OK
    }
    
    //释放Stmt
    fileprivate func releaseStmt(_ stmt: OpaquePointer) {
        sqlite3_finalize(stmt)
    }
    
    func execSqls(_ sqlarr : [String]) -> Bool {
        for item in sqlarr {
            if execSQL(item) == false {
                return false
            }
        }
        return true
    }
    
    func execSQL(_ sql : String) -> Bool {
        let errmsg : UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>? = nil
        if sqlite3_exec(appdb, sql, nil, nil, errmsg) == SQLITE_OK {
            return true
        }else{
            print("error \(errmsg)")
            return false
        }
    }
    
    //开启事务
    func beginTransaction() -> Bool{
        let sql = "begin transaction"
        return execSQL(sql)
    }
    
    //提交事务
    func commitTransaction() -> Bool{
        let sql = "commit transaction"
        return execSQL(sql)
    }
    
    //回滚事务
    func rollbackTransaction() -> Bool{
        let sql = "rollback transaction"
        return execSQL(sql)
    }
}

Model类

//
//  User.swift
//  SQLite3Demo
//
//  Created by targetcloud on 2016/12/7.
//  Copyright © 2016年 targetcloud. All rights reserved.
//

import UIKit

class User: NSObject {
    
    var name: String = ""
    var age: Int = 0
    var score: Float = 0.0
    var money: Float = 0.0
    
    init(name: String, age: Int, score: Float,money: Float = 100) {
        super.init()
        self.name = name
        self.age = age
        self.score = score
        self.money = money
    }
    
    init(dict : [String : Any]) {
        super.init()
        self.setValuesForKeys(dict)
    }
    
    class func allUserFromDB() -> [User]? {
        let sql = "SELECT name,age,score,money FROM t_User "
        let allUserDictArr = SQLite3Tool.shareInstance.queryAllStmt(sql)
        var userModelArrM = [User]()
        for dict in allUserDictArr {
            userModelArrM.append(User(dict: dict))
        }
        return userModelArrM
    }
    
    override func setValue(_ value: Any?, forUndefinedKey key: String) {
        
    }
    
    //辅助查询测试1
    class func queryAll() {
        SQLite3Tool.shareInstance.queryAll("select * from t_user")
    }
    
    //辅助查询测试2
    class func queryAllStmt() {
        SQLite3Tool.shareInstance.queryAllStmt("select * from t_user")
    }
    
    //辅助事务测试
    class func update(_ sql: String) -> Bool {
        return SQLite3Tool.shareInstance.execSQL(sql)
    }
    
    //辅助时间测试3
    func bindInsertUserWithTransaction(_ times:Int)  {
        let sql = "insert into t_user(name, age, score) values (?, ?, ?)"
        let db = SQLite3Tool.shareInstance.appdb
        var stmt: OpaquePointer? = nil
        if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
            print("prepare fail")
            return
        }
        SQLite3Tool.shareInstance.beginTransaction()
        for i in 0..<times {
            sqlite3_bind_int(stmt, 2, Int32(i))//索引从1开始,age使用i
            sqlite3_bind_double(stmt, 3, Double(self.score))
            //            let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
            sqlite3_bind_text(stmt, 1, self.name, -1, SQLite3Tool.shareInstance.SQLITE_TRANSIENT)
            if sqlite3_step(stmt) == SQLITE_DONE {
                print("step success")
            }
            sqlite3_reset(stmt)
        }
        SQLite3Tool.shareInstance.commitTransaction()
        sqlite3_finalize(stmt)
    }
    
    //辅助时间测试2
    func bindInsertUser()  {
        let sql = "insert into t_user(name, age, score) values (?, ?, ?)"
        let db = SQLite3Tool.shareInstance.appdb
        var stmt: OpaquePointer? = nil
        if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
            print("prepare fail")
            return
        }
        sqlite3_bind_int(stmt, 2, Int32(self.age))
        sqlite3_bind_double(stmt, 3, Double(self.score))
        //        let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
        sqlite3_bind_text(stmt, 1, self.name, -1, SQLite3Tool.shareInstance.SQLITE_TRANSIENT)
        if sqlite3_step(stmt) == SQLITE_DONE {
            print("step success")
        }
        sqlite3_reset(stmt)
        sqlite3_finalize(stmt)
    }
    
    //辅助时间测试1
    func insertUser()  {
        let sql = "insert into t_user(name, age, score) values ('\(self.name)', \(self.age), \(self.score))"
        if  SQLite3Tool.shareInstance.execSQL(sql)  {
            print("insert success")
        }
    }
    
    class func deleteUser(_ name: String)  {
        let sql = "delete from t_user where name = '\(name)'"
        if  SQLite3Tool.shareInstance.execSQL(sql)  {
            print("delete success")
        }
    }
    
    //插入方式2
    class func insertBatch(_ valuesBlock: @escaping () -> (Array<Array<Any>>)){
        SQLite3Tool.shareInstance.insertBatch("t_user", columnNameArray: ["name", "score","age"], valuesBlock: { () -> Array<Array<Any>> in
            return valuesBlock()
        })
    }
    
    //插入方式1
    func insert(){
        if  SQLite3Tool.shareInstance.insert("t_user", columnNameArray: ["name", "score","age"], valueArray: [self.name, self.score,self.age]){
            print("insert success")
        }
    }
    
    func insert2(){
        if  SQLite3Tool.shareInstance.insert("t_user", columnNameArray: ["name", "score","age"], values: self.name, self.score,self.age){
            print("insert2 success")
        }
    }
    
    //更新方式1 对象方法
    func updateUser(_ newUser: User)  {
        let sql = "update t_user set name = '\(newUser.name)', age = \(newUser.age), score = \(newUser.score) where name = '\(self.name)'"
        if  SQLite3Tool.shareInstance.execSQL(sql)  {
            print("update success")
        }
    }
    
    //更新方式2 类方法
    class func updateRecord(_ setStr: String, condition: String)-> Bool {
        return SQLite3Tool.shareInstance.updateRecord("t_user", setStr: setStr, condition: condition)
    }
    
}

测试类

//
//  ViewController.swift
//  SQLite3Demo
//
//  Created by targetcloud on 2016/12/7.
//  Copyright © 2016年 targetcloud. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var timeLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let userArr = User.allUserFromDB()
        print(userArr)
        timeLabel.text = "你查询到了 \(userArr?.count) 条记录"
    }

    //事务测试1
    @IBAction func transaction(){
        SQLite3Tool.shareInstance.beginTransaction()
        User.deleteUser("刘能")
        User.deleteUser("赵四")
        let user1 = User(name: "刘能", age:22, score: 100)
        let user2 = User(name: "赵四",age:33, score: 100)
        user1.insert()
        user2.insert2()
        let result1 = User.update("update t_user set money = money - 10 where name = '刘能'")
        let result2 = User.update("update t_user set money = money + 10 where name = '赵四'")
        if result1 && result2 {
            SQLite3Tool.shareInstance.commitTransaction()
        }else {
            SQLite3Tool.shareInstance.rollbackTransaction()
        }
    }
    
    //事务测试2
    @IBAction func transaction2(){
        SQLite3Tool.shareInstance.beginTransaction()
        User.deleteUser("铁娃")
        User.deleteUser("牛娃")
        let user1 = User(name: "铁娃", age:8, score: 60)
        let user2 = User(name: "牛娃",age:9, score: 50)
        user1.insertUser()
        user2.insert()
        let result1 = User.updateRecord("score = score - 10", condition: "name = '铁娃'")
        let result2 = User.updateRecord("score1 = score + 10", condition: "name = '牛娃'")//注意这里会失败,事务回滚了
        if result1 && result2{
            SQLite3Tool.shareInstance.commitTransaction()
        }else{
            SQLite3Tool.shareInstance.rollbackTransaction()
        }
    }
    
    //时间测试1 没开事务+sqlite3_exec
    @IBAction func timeTest(){
        let beginTime = CFAbsoluteTimeGetCurrent()
        let user = User(name: "时间测试1", age: 11, score: 11)
        for _ in 0..<10000 {
            user.insertUser()
        }
        let endTime = CFAbsoluteTimeGetCurrent()
        timeLabel.text = "没开事务+sqlite3_exec>\(endTime - beginTime)"
    }
    
    //时间测试2 没开事务+Stmt
    @IBAction func timeTest2(){
        let beginTime = CFAbsoluteTimeGetCurrent()
        let user = User(name: "时间测试2", age: 22, score: 22)
        for _ in 0..<10000 {
            user.bindInsertUser()
        }
        let endTime = CFAbsoluteTimeGetCurrent()
        timeLabel.text = "没开事务+Stmt>\(endTime - beginTime)"
    }
    
    //时间测试3 开事务
    @IBAction func timeTest3(){
        let beginTime = CFAbsoluteTimeGetCurrent()
        let user = User(name: "时间测试3", age: 33, score: 33)
        user.bindInsertUserWithTransaction(10000)
        let endTime = CFAbsoluteTimeGetCurrent()
        timeLabel.text = "开事务>\(endTime - beginTime)"
    }
    
    //时间测试4
    @IBAction func timeTest4(){
        let beginTime = CFAbsoluteTimeGetCurrent()
        let user = User(name: "时间测试4", age: 44, score: 44.43)
        User.insertBatch({() -> (Array<Array<Any>>) in
            var array = [[Any]]()//var array = Array<Array<Any>>()  //MARK:- 重点!性能最好
            for _ in 0...9999{
                array.append([user.name, user.score,user.age])
            }
            return array
        })
        let endTime = CFAbsoluteTimeGetCurrent()
        timeLabel.text = "开事务+Batch\(endTime - beginTime)"
    }
    
    //查询测试1
    @IBAction func allQuery(){
        User.queryAll()
    }
    
    //查询测试2
    @IBAction func stmtQuery(){
        User.queryAllStmt()
    }

}

效果图




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值