天天看點

SQLite基礎入門SQLite的介紹一、常用 SQL操作二、DDL語句的演練三、DML語句的演練 四、DQL語句的演練五、SQLite核心對象核心對象 & 核心接口六、Swift 中使用 SQLite八、查詢資料九、批量插入

SQLite的介紹

在iOS存儲資料的方式

  • 為什麼要存儲資料?
    • 在開發中展示的大部分資料是從網絡伺服器中加載出來的
    • 但是如果所有的資料每次都從網絡請求,則會有相應的問題.
      • 浪費使用者的流量
      • 在使用者沒有網絡時,無法正常顯示頁面
    • 将網絡資料存儲在本地,不用每次都加載
      • 減少使用者網絡流量開銷
      • 使用者在沒有網絡時,依然可以展示一些資料
  • 存儲方式
    • Plist(NSArray\NSDictionary)
    • Preference(偏好設定\NSUserDefaults)
    • NSCoding(NSKeyedArchiver\NSkeyedUnarchiver)
    • SQLite
    • Core Data
  • 在所有的存儲方式中,SQLite速度最快,效率最高.

SQLite的介紹

  • 什麼是SQLite
    • SQLite是一款輕型的嵌入式關系資料庫
    • 它占用資源非常的低,在嵌入式裝置中,可能隻需要幾百K的記憶體就夠了
    • 目前廣泛應用于移動裝置中存儲資料(Android/iOS)
    • 處理資料的速度非常快,效率非常高
  • 什麼是資料庫
    • 資料庫(Database)是按照資料結構來組織、存儲和管理資料的倉庫(類似于excel表格)
    • 資料庫可以分為2大種類(了解)
      • 關系型資料庫(主流)
      • 對象型資料庫
  • 常見的關系資料庫
    • PC端:Oracle、MySQL、SQL Server、Access、DB2
    • 嵌入式\移動用戶端:SQLite

關系型資料庫

  • 關系資料庫和Excel表格非常非常相似
    • 一個Excel檔案可以了解成一個資料庫
    • 在一個Excel檔案中可以有多張表
    • 一張表中可以有多組資料
    • 每組資料都有自己對應的屬性值
  • 關系資料庫的特點
    • 一個 

      字段(COL)

       存儲一個值,類似于對象的一個屬性
    • 一 

      行(ROW)

       存儲一條記錄,類似于一個對象
    • 一個 

      表(TABLE)

       存儲一系列資料,類似于對象數組
    • 多個 

       之間存在一定 

      關系

      ,類似于對象之間的關系,例如:一條微網誌資料中包含使用者記錄

SQLite的使用

  • SQLite的使用步驟
    • 建立一個資料庫->一個用于存儲資料的檔案
    • 資料庫中建立表->一個表中用于記錄一系列資料
      • 建立表時需要指定該表有哪些字段
      • 比如學生表有:學号/姓名/年齡/身高等
    • 在表中添加資料
    • 之後對表進行增删改查操作
      • 增:給表中添加資料
      • 删:從表中删除資料
      • 改:修改表中原有資料
      • 查:查詢表中原有資料
  • 學習SQLite
    • 真正在使用SQLite時,一定是通過代碼來使用的
    • 但是在真正開始代碼前可以先學習SQLite的基本操作
    • 在Mac中如何學習呢?
      • 可以通過指令行
      • 可以通過圖形化界面工具(推薦)
  • Navicat軟體的介紹
    • Navicat是一款著名的資料庫管理軟體,支援大部分主流資料庫(包括SQLite)
    • 收費軟體(4998RMB)

術語

  • 字段(

    Field/Col

    ):一個字段存儲一個值,可以存儲 

    INTEGER

    REAL

    TEXT

    BLOB

    NULL

     五種類型的資料
    • SQLite 在存儲時,本質上并不區分準确的資料類型
  • 主鍵:

    Primary Key

    • 為什麼需要主鍵?
      • 如果t_student表中就name和age兩個字段,而且有些記錄的name和age字段的值都一樣時,那麼就沒法區分這些資料,造成資料庫的記錄不唯一,這樣就不友善管理資料
      • 良好的資料庫程式設計規範應該要保證每條記錄的唯一性,為此,增加了主鍵限制
      • 也就是說,每張表都必須有一個主鍵,用來辨別記錄的唯一性
    • 主鍵的設計原則
      • 主鍵應當是對使用者沒有意義的
      • 永遠也不要更新主鍵
      • 主鍵應當由計算機自動更新增長
      • 主鍵不能為空

一、常用 SQL操作

如何在程式中使用SQLite

  • 真實使用SQLite是在程式中使用的
  • 如何在程式運作過程中操作資料庫中的資料
    • 那得先學會使用SQL語句
  • 什麼是SQL
    • SQL(structured query language):結構化查詢語言
    • SQL是一種對資料庫中的資料進行

      定義

      操作

      的語言
    • SQL語言簡潔,文法簡單,好學好用
  • 什麼是SQL語句
    • 使用SQL語言編寫出來的句子\代碼,就是SQL語句
    • 在程式運作過程中,要想操作(增删改查,CRUD)資料庫中的資料,必須使用SQL語句
  • SQL語句的特點
    • 不區分大小寫(比如資料庫認為user和UsEr是一樣的)
    • 每條語句都必須以分号

      ;

      結尾
  • SQL中的常用關鍵字
    • select、insert、update、delete、from、create、where、desc、order、by、group、table、alter、view、index等等
    • 資料庫中不可以使用關鍵字來命名表、字段

SQL語句的種類

  • 資料定義語句(DDL:Data Definition Language)
    • 包括create和drop等操作
    • 在資料庫中建立新表或删除表(create table或 drop table)
  • 資料操作語句(DML:Data Manipulation Language)
    • 包括insert、update、delete等操作
    • 上面的3種操作分别用于添加、修改、删除表中的資料
  • 資料查詢語句(DQL:Data Query Language)
    • 可以用于查詢獲得表中的資料
    • 關鍵字select是DQL(也是所有SQL)用得最多的操作
    • 其他DQL常用的關鍵字有where,order by,group by和having

二、DDL語句的演練

删除表

  • 删除表的格式
  • 語句說明
    • DROP TABLE:删除表
    • IF EXISTS:存在則删除
    • '表名':要删除的表的名稱

建立表

  • 建立表的格式
CREATE TABLE IF NOT EXISTS '表名' (
        '字段名' 類型(INTEGER, REAL, TEXT, BLOB)
                NOT NULL    不允許為空
                PRIMARY KEY    主鍵
                AUTOINCREMENT 自增長,
        '字段名2' 類型,
        ...
    )
           
  • 具體用法
CREATE TABLE IF NOT EXISTS 't_student' (
     "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
     "name" TEXT,
     "age" INTEGER,
     "height" REAL
)
           
  • 語句說明
    • CREATE TABLE:建立一張表
    • IF NOT EXISTS:不存在則建立
    • 't_student':表的名稱
    • NOT NULL:不允許為空
    • PRIMARY KEY:主鍵
    • AUTOINCREMENT:自動增加
    • 'id' INTEGER:有一個ID字段,類型是INTEGER

三、DML語句的演練

插入資料

  • 插入資料的用法
  • 語句說明
    • INSERT INTO: 插入資料
    • 't_student': 在哪一個表中插入資料
    • (資料的字段): 給哪些字段插入資料
    • VALUES ('why', 18, 1.88): 插入的具體值

更新資料

  • 更新資料的寫法:
  • 語句說明
    • UPDATE: 跟新資料
    • 't_student': 在哪一個表中更新資料
    • SET 字段 = '值': 更新怎樣的資料
    • WHERE 條件判斷: 更新哪些資料
  • 具體用法
UPDATE t_student
    SET name = 'MM'
    WHERE age = ;

UPDATE t_student
    SET name = 'WW'
    WHERE age is ;

UPDATE t_student
    SET name = 'XXOO'
    WHERE age < ;

UPDATE t_student
    SET name = 'NNMM'
    WHERE age <  and score > ;

/*更新記錄的name*/
UPDATE t_student SET name = 'zhangsan';
           

删除資料

  • 删除資料的用法
DELETE FROM t_student;
DELETE FROM t_student WHERE age < ;
           
  • 用法說明
    • DELETE FROM: 從表中删除資料
    • t_student : 表名
    • 可以跟條件也可以不跟:不跟表示删除所有的資料

四、DQL語句的演練

DQL語句

  • 查詢語句
查詢語句
1.基本查詢
SELECT * FROM t_student;

2.查詢某些字段
SELECT name, age FROM t_student;

3.通過條件判斷來查詢對應的資料(年齡大于等于25)
SELECT * FROM t_student WHERE age >= 25;

4.通過條件判斷來查詢對應的資料(名字以l開頭),使用like關鍵字
SELECT * FROM t_student WHERE name like '%l%';

5.計算個數
1>計算一共多少列
SELECT count(*) FROM t_student;
2>計算某一個列個數
SELECT count(age) FROM t_student;

6.排序
1> 升序 ASC (預設是升序)
SELECT * FROM t_student ORDER BY age;
2> 降序 DESC
SELECT * FROM t_student ORDER BY age DESC;
3> 按照年齡升序排序,如果年齡相同,按照名字的降序排列

7.起别名
1> 給列起别名(as可以省略)
SELECT name AS myName, age AS myAge FROM t_student;
2> 給表起别名
SELECT s.name, s.age FROM t_student as s;

8.limit
格式:SELECT * FROM t_student LIMIT 數字1,數字2;
1>數字1的意思是前面跳過多少條資料
2>數字2的意思是本次查詢多少條資料
SELECT * FROM t_student LIMIT 9, 3;
SELECT * FROM t_student LIMIT 5; 跳過0條資料,取5條資料
           

五、SQLite核心對象

核心對象 & 核心接口

核心對象

  1. database_connection
    • 由 

      sqlite3_open

       函數建立并傳回
    • 在使用其他 SQLite 接口函數之前,必須先獲得database_connnection對象
  2. prepared_statement
    • 可以簡單的将它視為編譯後的SQL語句

核心接口

  1. sqlite3_open
    • 可以打開已經存在的資料庫檔案
    • 如果資料庫不存在,可以建立新的資料庫檔案
    • 傳回的 

      database_connection

       對象是其他 

      SQLite APIs

       的句柄參數
    • 可以在多個線程之間共享該對象指針
  2. sqlite3_prepare
    • 将 

      SQL

       文本轉換為 

      prepared_statement

       對象
    • 不會

      執行

      指定的 

      SQL

       語句
    • 隻是将 

      SQL

       文本初始化為

      執行的狀态
  3. sqlite3_step
    • 執行一次 

      sqlite3_prepare

       函數傳回的 

      prepared_statement

       對象
    • 執行完該函數後,

      prepared_statement

       對象的内部指針将指向其傳回結果集的第一行
    • 如果要獲得後續的資料行,則需要不斷地調用該函數,直到所有的資料行周遊完畢
    • 對于 

      INSERT

      UPDATE

       和 

      DELETE

       等 

      DML

       語句,執行一次即可完成
  4. sqlite3_column
    • 用于擷取目前行指定列的資料
    • 以下函數分别對應不同的資料類型
      • sqlite3_column_blob

      • sqlite3_column_bytes

      • sqlite3_column_bytes16

      • sqlite3_column_double

      • sqlite3_column_int

      • sqlite3_column_int64

      • sqlite3_column_text

      • sqlite3_column_text16

      • sqlite3_column_type

      • sqlite3_column_value

      • sqlite3_column_count

        • 用于擷取目前結果集中的字段數量
  5. sqlite3_finalize
    • 銷毀 

      prepared_statement

       對象,否則會造成記憶體洩露
  6. sqlite3_close
    • 關閉之前打開的 

      database_connection

       對象
    • 所有和該對象相關的 

      prepared_statements

       對象都必須在此之前被銷毀

六、Swift 中使用 SQLite

準備工作

  • 添加 

    libsqlite3.dylib

  • 建立 

    SQLite-Bridge.h

    • SQLite3

       架構是一套 

      C

       語言的架構,是以需要添加橋接檔案
  • 選擇 

    項目

    -

    TARGETS

    -

    Build Settings

    ,搜尋 

    Bridg

  • 在 

    Objective-C Bridging Header

     中輸入 

    項目名/SQLite-Bridge.h

    • 如果之前設定過橋接檔案,可以直接使用
編譯測試

SQLiteManager

與網絡接口的獨立類似,資料庫的底層操作,也應該有一個獨立的對象單獨負責

SQLiteManager

 單例

  • 建立 

    SQLiteManager.swift

    ,并且實作以下代碼:
class SQLiteManager {

    private static let instance = SQLiteManager()
    class func sharedSQLiteManager() -> SQLiteManager {
        return instance
    }
}
           

資料庫通路操作需求

  1. 建立資料庫 -> 有存儲資料的檔案
  2. 建立資料表 -> 每一張資料表存儲一類資料
  3. 利用 

    SQL 指令

     實作增/删/查/改,并在 UI 中顯示

建立&打開資料庫

/// 全局資料庫指針
private var db: COpaquePointer = nil

///  打開資料庫
///
///  :param: dbname 資料庫檔案名
///
///  :returns: 是否成功
func openDB(dbname: String) -> Bool {

    // 資料庫檔案的完整路徑
    let filePath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last?.stringByAppendingPathComponent(dbname)

    // 打開資料庫,如果資料庫不存在會建立
    if sqlite3_open(filePath!.cStringUsingEncoding(NSUTF8StringEncoding)!, &db) != SQLITE_OK {
        println("資料庫建立失敗")
        return false
    }

    println("資料庫建立成功 \(filePath)")
    return true
}
           
代碼小結
  • 建立資料庫需要給定完整的資料庫路徑
  • 資料庫路徑名是 C 語言的字元串,需要使用 

    cStringUsingEncoding

     轉換
  • sqlite3_open

     函數會打開資料庫,如果資料庫不存在,會

    建立一個空的資料庫

    ,并且傳回資料庫指針(句柄)
  • 後續的所有資料庫操作,都針基于此 

    資料庫句柄

     進行

打開資料庫

  • 在 

    AppDelegate

     中添加以下代碼
SQLiteManager.sharedSQLiteManager().openDB("my.db")
           
代碼小結
  • SQLite

     資料庫是直接儲存在沙盒中的一個檔案,隻有目前應用程式可以使用
  • 在移動端開發時,資料庫通常是以 

    持久式

     連接配接方式使用的
  • 所謂 

    持久式連接配接

     指的是隻做一次 

    打開資料庫

     的操作,永遠不做 

    關閉

     資料庫的操作,進而可以提高資料庫的通路效率

建立資料表

  • 如果是第一次運作,打開資料庫之後,隻能得到一個空的資料,沒有任何的資料表
  • 為了讓資料庫正常使用,在第一次打開資料庫後,需要執行 

    創表

     操作
注意:創表操作本質上是通過執行 

SQL

 語句實作的
  • 執行 

    SQL

     語句函數
///  執行 SQL
///
///  :param: sql SQL 語句
///
///  :returns: 是否成功
func execSQL(sql: String) -> Bool {
    let cSQL = sql.cStringUsingEncoding(NSUTF8StringEncoding)!

    /**
        參數
        1. 已經打開的資料庫句柄
        2. 要執行(evaluated)的 SQL 語句
        3. 執行完成後的回調,通常為 nil
        4. 回調函數的第一個參數,通常為 nil
        5. 錯誤,通常為 nil
    */
    return sqlite3_exec(db, cSQL, nil, nil, nil) == SQLITE_OK
}
           
  • 建立資料表
///  建立資料表
private func createTables() -> Bool {

    let sql = "CREATE TABLE IF NOT EXISTS T_Book ( \n" +
        "'id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n" +
        "'name' TEXT NOT NULL, \n" +
        "'price' REAL NOT NULL \n" +
        ")"

    return execSQL(sql)
}
           
  • 調整 

    openDB

     函數
println("資料庫建立成功 \(filePath)")

// 建立資料表
let result = createTables()
if !result {
    db = nil
}
println("創表" + (result ? "成功": "失敗"))

return result
           
代碼小結
  • 創表 

    SQL

     可以從 

    Navicat

     中粘貼,然後做一些處理
    • 将 

      "

       替換成 

      '

    • 在每一行後面增加一個 

      \n

       防止字元串拼接因為缺少空格造成 

      SQL

       語句錯誤
    • 在 

      表名

       前添加 

      IF NOT EXISTS

      防止因為資料表存在出現錯誤

新增資料

建立 

Book

 模型
class Book: NSObject {

    var name: String
    var price: Double

    init(name: String, price: Double) {
        self.name = name
        self.price = price
    }

    ///  将目前對象資訊插入資料庫
    ///
    ///  :returns: 是否成功
    func insertIntoDB() -> Bool {
        let sql = "INSERT INTO T_Book (name, price) VALUES ('\(name)', \(price))"

        return SQLiteManager.sharedSQLiteManager().execSQL(sql)
    }
}
           
  • 測試插入資料
///  插入一本書
func insertOneBook() {
    if Book(name: "iPhone", price: ).insertIntoDB() {
        println("插入成功")
    }
}
           
  • 測試批量插入
///  插入10000本書
func insertBooks() {
    let start = CFAbsoluteTimeGetCurrent()
    println("開始...")
    for  in ..< {
        let n = "iPhone + \(arc4random_uniform())"
        let p =  + Double(arc4random_uniform())

        Book(name: n, price: p).insertIntoDB()
    }
    println("完成 \(CFAbsoluteTimeGetCurrent() - start)")
}
           

八、查詢資料

準備 SQL 語句

///  執行 SQL 獲得結果集合
///
///  :param: sql 加載資料
///
///  :returns: 結果集合
func execRecordSet(sql: String) -> [AnyObject]? {

    let cSQL = sql.cStringUsingEncoding(NSUTF8StringEncoding)!
    // 編譯後的 SQL 語句對象
    var stmt: COpaquePointer = nil

    /**
        準備 SQL 語句

        參數
        1. 已經打開的資料庫句柄
        2. 要執行的 SQL
        3. 以位元組為機關的 SQL 最大長度,傳入 -1 會自動計算
        4. SQL 語句位址
        5. 未使用的指針位址,通常傳入 nil
    */
    var recordSet: [AnyObject]?
    if sqlite3_prepare_v2(db, cSQL, -1, &stmt, nil) == SQLITE_OK {

        // 判斷是否讀取到記錄
        while sqlite3_step(stmt) == SQLITE_ROW {
            println("獲得記錄")
        }
    }

    // 釋放語句對象
    sqlite3_finalize(stmt)

    return recordSet
}
           

代碼小結

  • 這一部分的工作可以看作是對字元串的 SQL 語句進行編譯,并且檢查是否存在文法問題
  • 編譯成功後通過 

    sqlite3_step

     執行 SQL,每執行一次,擷取一條記錄
  • 通過 

    while

     循環直至執行完畢
  • 注意,指令執行完畢後需要釋放
  • 在 

    Book

     模型中增加測試函數
///  加載書
class func loadBooks() {
    let sql = "SELECT name, price FROM T_Book;"

    SQLiteManager.sharedSQLiteManager().execRecordSet(sql)
}
           

擷取記錄行中的内容

  • 周遊每一列的資訊
// 判斷是否讀取到記錄
while sqlite3_step(stmt) == SQLITE_ROW {
    println("列數 \(sqlite3_column_count(stmt))")
    // 列數
    let colCount = sqlite3_column_count(stmt)
    for i in ..<colCount {
        let type = sqlite3_column_type(stmt, i)
        let name = sqlite3_column_name(stmt, i)

        print(" \(i) 列 \(type) \(name)")

        switch type {
        case SQLITE_INTEGER:
            let i = sqlite3_column_int64(stmt, i)
            println("int \(i)")
        case SQLITE_FLOAT:
            let d = sqlite3_column_double(stmt, i)
            println("double \(d)")
        case SQLITE_BLOB:
            let b = sqlite3_column_blob(stmt, i)
            println("blob \(b)")
        case SQLITE_NULL:
            println(NSNull())
        case SQLITE3_TEXT:
            let t = sqlite3_column_text(stmt, i)
            println("text \(t)")
        default:
            println("不支援的類型 \(type)")
        }
    }
}
           
  • 擷取字段名稱
let cname = sqlite3_column_name(stmt, i)
let colName = String(CString: cname, encoding: NSUTF8StringEncoding)
           
  • 定義記錄字典
// 定義記錄字典
var record = [String: AnyObject]()
           
  • 将解析内容添加到字典
for i in ..<colCount {
    // 字段名稱
    let cname = sqlite3_column_name(stmt, i)
    let colName = String(CString: cname, encoding: NSUTF8StringEncoding)!

    let type = sqlite3_column_type(stmt, i)
    print(" \(i) 列 \(type) \(colName)")

    switch type {
    case SQLITE_INTEGER:
        record[colName] = Int(sqlite3_column_int64(stmt, i))
    case SQLITE_FLOAT:
        record[colName] = sqlite3_column_double(stmt, i)
    case SQLITE_NULL:
        println(NSNull())
        record[colName] = NSNull()
    case SQLITE3_TEXT:
        let t = UnsafePointer<Int8>(sqlite3_column_text(stmt, i))
        record[colName] = String(CString: t, encoding: NSUTF8StringEncoding)
    default:
        println("不支援的類型 \(type)")
    }
}
           
  • 抽取函數
///  從 stmt 中擷取記錄字典
///
///  :param: stmt stmt
///
///  :returns: 字典資訊
private func recordDict(stmt: COpaquePointer) -> [String: AnyObject]? {
    // 列數
    let colCount = sqlite3_column_count(stmt)
    if colCount <= 0 {
        return nil
    }

    // 定義記錄字典
    var record = [String: AnyObject]()
    for i in 0..<colCount {
        // 字段名稱
        let cname = sqlite3_column_name(stmt, i)
        let colName = String(CString: cname, encoding: NSUTF8StringEncoding)!

        let type = sqlite3_column_type(stmt, i)
        print(" \(i) 列 \(type) \(colName)")

        switch type {
        case SQLITE_INTEGER:
            record[colName] = Int(sqlite3_column_int64(stmt, i))
        case SQLITE_FLOAT:
            record[colName] = sqlite3_column_double(stmt, i)
        case SQLITE_NULL:
            println(NSNull())
            record[colName] = NSNull()
        case SQLITE3_TEXT:
            let t = UnsafePointer<Int8>(sqlite3_column_text(stmt, i))
            record[colName] = String(CString: t, encoding: NSUTF8StringEncoding)
        default:
            println("不支援的類型 \(type)")
        }
    }
    return record
}
           
  • 提取結果集函數
///  執行 SQL 獲得結果集合
///
///  :param: sql 加載資料
///
///  :returns: 結果集合
func execRecordSet(sql: String) -> [[String: AnyObject]]? {

    let cSQL = sql.cStringUsingEncoding(NSUTF8StringEncoding)!
    // 編譯後的 SQL 語句對象
    var stmt: COpaquePointer = nil

    /**
        準備 SQL 語句

        參數
        1. 已經打開的資料庫句柄
        2. 要執行的 SQL
        3. 以位元組為機關的 SQL 最大長度,傳入 -1 會自動計算
        4. SQL 語句位址
        5. 未使用的指針位址,通常傳入 nil
    */
    var recordList: [[String: AnyObject]]?
    if sqlite3_prepare_v2(db, cSQL, -1, &stmt, nil) == SQLITE_OK {

        // 執行個體化數組
        recordList = [[String: AnyObject]]()
        // 判斷是否讀取到記錄
        while sqlite3_step(stmt) == SQLITE_ROW {
            if let record = recordDict(stmt) {
                recordList!.append(record)
            }
        }
    }

    // 釋放語句對象
    sqlite3_finalize(stmt)

    return recordList
}
           
  • 修改加載書函數
///  加載書
class func loadBooks() -> [Book]? {
    let sql = "SELECT name, price FROM T_Book;"

    if let list = SQLiteManager.sharedSQLiteManager().execRecordSet(sql) {
        var books = [Book]()

        for record in list {
            let n = record["name"] as! String
            let p = record["price"] as! Double

            books.append(Book(name: n, price: p))
        }

        return books
    }

    return nil
}
           

九、批量插入

在 SQLite 中如果要批量插入資料,通常需要引入 

事務的概念

事務

  • 在準備做

    大規模資料操作前

    ,首先開啟一個事務,儲存操作前的資料庫的狀态
  • 開始資料操作
  • 如果資料操作成功,

    送出

    事務,讓資料庫更新到資料操作後的狀态
  • 如果資料操作失敗,

    復原

    事務,讓資料庫還原到操作前的狀态
  • 事務處理函數
///  開啟事務
///
///  :returns: 是否成功
func beginTransaction() -> Bool {
    return sqlite3_exec(db, "BEGIN TRANSACTION;", nil, nil, nil) == SQLITE_OK
}

///  送出事務
///
///  :returns: 是否成功
func commitTransaction() -> Bool {
    let result = sqlite3_exec(db, "COMMIT TRANSACTION;", nil, nil, nil) == SQLITE_OK

    if !result {
        let errmsg = String.fromCString(sqlite3_errmsg(db))
        println("送出錯誤 \(errmsg)")
    }

    return result
}

///  復原事務
///
///  :returns: 是否成功
func rollbackTransaction() -> Bool {
    return sqlite3_exec(db, "ROLLBACK TRANSACTION;", nil, nil, nil) == SQLITE_OK
}