iOS之SQLite3

1、SQLite 简介

1.1、 什么是 SQLite?

SQLite 是一个进程内的软件库,使用纯C语言构建的,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库一样,您不需要在系统中配置。

SQLite引擎不是与程序通信的独立进程,而是按应用程序需求进行静态或动态连接到程序中成为它的一个主要部分,直接访问其存储文件。SQLite 主要的通信协议是在编程语言内的直接API调用,这在消耗总量、延迟时间和整体简单性上有积极的作用。整个数据库(定义、表、索引和数据本身)都存储在一个单一的文件中。它的简单设计是通过在开始一个事务的时候锁定整个数据文件而完成的。

1.2、 终端安装 Sqlite3

从 SQLite 下载 sqlite-autoconf-*.tar.gz :

MacBook-Pro ~ % cd /Users/wanst/Desktop/sqlite-autoconf-3310100
MacBook-Pro sqlite-autoconf-3310100 % ./configure --prefix=/usr/local
MacBook-Pro sqlite-autoconf-3310100 % make
MacBook-Pro sqlite-autoconf-3310100 % make install

使用下列命令进行验证:

MacBook-Pro ~ % sqlite3
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite>

2、SQLite 命令

标准 SQLite 命令包括 CREATESELECTINSERTUPDATEDELETEDROP。这些命令基于它们的操作性质可分为以下几种:

DDL数据定义命令 描述 (针对表的操作)
CREATE 创建一张新表
ALTER 修改数据库中存在的某张表
DROP 删除一张表
DML数据操作命令 描述 (针对表内数据的操作)
INSERT 创建记录
UPDATE 修改记录
DELETE 删除记录
DQL数据查询命令 描述 (针对表内数据的操作)
SELECT 从一个或多个表中检索某些记录
2.1、CREATE 命令:建表

SQLite 的 CREATE TABLE 语句用于在任何给定的数据库整中创建一张新表。创建基本表,涉及到命名表、定义列及每一列的数据类型。

CREATE TABLE 语句的基本语法如下:

CREATE TABLE 表名称 (
    列名称1 数据类型,
    列名称2 数据类型,
    列名称3 数据类型,
    ....
)
2.1.1、自动递增 Autoincrement

关键字 Autoincrement 用于表中的INTEGER字段值自动递增,在创建表时在特定的列名称上使用。

sqlite_sequence 表信息

当 SQLite 数据库中包含自增列时,会自动建立一个名为 sqlite_sequence 的表。这个表包含两列:nameseq

  • name 记录自增列所在的表;
  • seq 记录当前序号;

如果想把某个自增列的序号归零,只需要修改 sqlite_sequence 表。

UPDATE sqlite_sequence SET seq = 0
2.1.2、约束

约束是添加在表的数据列上强制执行的规则。约束可以限制插入到表中的数据类型,确保数据库中数据的准确性和可靠性。

  • PRIMARY KEY : 唯一标识表中的每条记录的主键,每张表中有且只有一个主键;主键可以由一个或多个字段组成,当多个字段作为主键,它们被称为复合键
  • NOT NULL :默认情况下,列可以保存 NULL 值;如果不想某列有 NULL值,那么需要在该列上定义NOT NULL 约束,指定在该列上不允许 NULL 值;
    NULL 与没有数据是不一样的,它代表着未知的数据;
  • UNIQUE :防止某列存在两个记录具有相同的值;每个表可以有多个 UNIQUE 约束;
  • DEFAULT :在 INSERT INTO 语句没有提供一个特定的值时,为列提供一个默认值;
  • CHECK :限制该列值的范围;
  • FOREIGN KEY :一个表中的外键指向另一个表中的 UNIQUE 键;

约束可以是列级或表级。列级约束仅适用于列,表级约束被应用到整个表。

eg:
CREATE TABLE Persons (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT UNIQUE NOT NULL,//名字唯一且非空
    age INTEGER CHECK (age>0),//年纪要求 >0
    sex boolean DEFAULT YES,//性别默认为 男性
    time DATE DEFAULT (datetime('now','localtime')),//默认为当前时间
)
 
CREATE TABLE Cars (
    id INTEGER PRIMARY KEY,
    owners TEXT NOT NULL,
    brand TEXT,
    price DOUBLE DEFAULT 0.0,
    time DATE DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (owners) REFERENCES Persons(name)//为该表设置一个外键
)

时区:SQLite 数据库默认的时区是 UTC,比东八区少了八个小时;因此使用 CURRENT_TIMESTAMP 会出现和系统时间不一致的问题;使用 datetime('now','localtime') 函数获取当前时间!

2.2、删表
2.2.1、删除表本身

SQLite 的 DROP TABLE 语句用来删除表定义及其所有相关数据、索引、触发器、约束和该表的权限规范。

DROP TABLE IF EXISTS Persons

注意: 一旦一个表被删除,表中所有信息也将永远丢失。

2.2.2、 删除表内的数据

如果我们仅仅需要清空表内的数据,但不删除表本身,可以使用

TRUNCATE TABLE IF EXISTS Persons

但令人遗憾的是 SQLite3 并不支持TRUNCATE命令;为达目的需要使用 DELETE命令:

DELETE FROM Persons

如果创建表时,设置主键自增长 AUTOINCREMENT ,那么上述DELETE命令就无法将递增数归零,还需要更新 sqlite_sequence 的表内数据:

//将表 sqlite_sequence 内 seq 列的数据全部设置为 0
UPDATE sqlite_sequence SET seq = 0
2.3、更改表结构

SQLite 的 ALTER TABLE 命令不通过执行一个完整的转储和数据的重载来修改已有的表。

可以使用该命令重命名表、在已有的表中添加列:

//重命名表
ALTER TABLE Persons RENAME TO Persons_New

//在表中添加列
ALTER TABLE Persons ADD birthday TEXT

SQLite 不支持 ALTER TABLE 的其他操作:

//在表中删除列
ALTER TABLE Persons DROP COLUMN birthday

//在表中删除索引
ALTER TABLE Persons DROP INDEX birthday

//改变数据类型
ALTER TABLE Persons ALTER COLUMN birthday INTEGER
2.4、增

INSERT INTO 语句用于向表格中插入新的行:若有唯一键,则只能插入一次;再次插入则操作失败!

@"INSERT INTO Persons (name,age) VALUES (? , ?)",@"张三",@(69)

一次插入多个数据

@"INSERT INTO Persons (name,age) VALUES (? , ?),(? , ?),(? , ?)",@"张三",@(69),@"李四",@(25),@"王五",@(36)

如果插入的数据不要求唯一性,那么可以重复插入!但是如果要求某个字段唯一,那么重复插入就会失败!
此时我们需要先判断表中是否存在该数据,如果没有,则插入;如果有该条数据,就去更新!

2.4.1、覆盖性插入

REPLACE INTO 语句提供了覆盖性插入的功能:如果新插入行的主键或唯一键在表中已经存在,则会删除原有记录并插入新行;如果在表中不存在,则直接插入!

@"REPLACE INTO Persons (name,age) VALUES (? , ?)",@"张三",@(69)
2.5、查

SELECT 语句用于从 SQLite 数据库表中获取数据,以结果表的形式返回数据。这些结果表也被称为结果集。

//获取多行数据
@"SELECT * FROM Persons WHERE age = < 20"

//获取某一行
@"SELECT * FROM Persons WHERE name = ?",value
@"SELECT * FROM Persons WHERE %@ = '%@'",key,value

//获取某一列
@"SELECT name FROM Persons"

//获取全部数据
@"SELECT * FROM Persons"
2.5.1、为查询结果排序

ORDER BY 语句根据指定的列对结果集进行排序:默认按照升序对记录进行排序;可以使用 DESC 关键字按照降序对记录进行排序。

//以年龄大小排序
@"SELECT * FROM Persons ORDER BY age"

//以年龄大小排序:逆序
@"SELECT * FROM Persons ORDER BY age DESC"
2.5.2、根据指定条件查询

SELECT 语句中插入 WHERE 子句可以有条件地从表中选取数据。

WHERE子句比较运算符 描述
= 等于
<> 不等于
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于
WHERE子句逻辑运算符 描述
AND 多个条件同时成立
OR 多个条件只要有一个成立
BETWEEN 在某个范围内
LIKE 模糊查询
IS 等价于 =,根据指定值来查询表
IN 类似于 =,只不过可以根据多个指定值来查询表
NOT 否定运算符,所用的逻辑运算符的对立面
Ⅰ、 多个条件过滤

ANDOR 运算符可在 WHERE 语句中把两个或多个条件结合起来:

  • 如果第一个条件和第二个条件都成立,则 AND 运算符显示一条记录;
  • 如果第一个条件和第二个条件中只要有一个成立,则 OR 运算符显示一条记录;
//筛选表中年纪小于 25 岁或者名字以 a 开头的人
@"SELECT * FROM Persons WHERE age < 25 OR name LIKE 'a%'"

//筛选表中年纪小于 30 岁并且名字第三个字母是 b 的人
@"SELECT * FROM Persons WHERE age < 30 AND name LIKE '__b%'"
Ⅱ、 模糊查询:LIKE 运算符

在搜索数据库中的数据时,LIKE 操作符 与 SQL 通配符一起使用实现模糊查询的功能。

SQL 通配符 描述
% 替代零个、一个或多个数字或字符
_ 仅替代一个字符
[charlist] 字符列中的任何单一字符
[^charlist] 不在字符列中的任何单一字符
[!charlist] 不在字符列中的任何单一字符
//筛选表中名字以 a 开头的人
@"SELECT * FROM Persons WHERE name LIKE 'a%'"

//筛选表中名字以 g 结尾的人
@"SELECT * FROM Persons WHERE name LIKE '%g'"

//筛选表中名字包含 en 的人
@"SELECT * FROM Persons WHERE name LIKE '%en%'"

//筛选表中名字第三个字母是 b 的人 : _表示一个任意字符
//前两个是任意字符,第三个是b字符的进行匹配
@"SELECT * FROM Persons WHERE name LIKE '__b%'"

//筛选表中名字包含数字的人
@"SELECT * FROM Persons WHERE name LIKE '%[0-9]%'"

//筛选表中名字不是以字母结尾的人
@"SELECT * FROM Persons WHERE name LIKE '%[^a-z]'"
Ⅲ、 BETWEEN 运算符

BETWEEN 运算符查询在某个范围内的数据:

//查询年龄在 1~4 岁的所有人
@"SELECT * FROM Persons WHERE age > 0 AND age < 5"

//BETWEEN 在数据库有优化,性能比较好,高于上面的语句
@"SELECT * FROM Persons WHERE age BETWEEN 1 AND 4"
Ⅳ、 IN 运算符

IN 类似于 =,只不过可以根据多个指定值来查询表

//筛选表中年纪为 25 、27、 29 岁的所有人
@"SELECT * FROM Persons WHERE age IN ( 25, 27 ,29 )"

//筛选表中年纪不是 25 、27 岁的所有人
@"SELECT * FROM Persons WHERE age NOT IN ( 25, 27)"
Ⅴ、 IS 运算符

IS 等价于 =,根据指定值来查询表

//筛选表中年纪为 25 岁的所有人
@"SELECT * FROM Persons WHERE age IS 25"

//筛选表中年纪不是 27 岁的所有人
@"SELECT * FROM Persons WHERE age IS NOT 27"
2.6、删

DELETE 用于删除表中已有的记录。可以使用带有 WHERE 子句的 DELETE 命令来删除选定行,否则所有的记录都会被删除。

//如果表中有多条 age  < 20,则全部删除
@"DELETE FROM Persons WHERE age < ?",@(20)

//删除表中全部数据
@"DELETE FROM Persons"
2.7、改

UPDATE 用于修改表中已有的记录。可以使用带有 WHERE 子句的 UPDATE 命令来更新选定行,否则所有的行都会被更新。

/** 更新多行中的一个列
 * @note 被修改的数据如果具有唯一性,则只能修改其中一行数据
 */
@"UPDATE Persons SET name = ? WHERE age < ?”,@“少年”,@(30)

//更新某一行中的一个列
@"UPDATE Persons SET name = ? WHERE age = ?",@"李四",@(12)

//更新某一行中的若干列
@"UPDATE Persons SET name = ?,sex = ? WHERE name = ?",@(12),@(YES),@“李四"

3、SQLite 事务

3.1、事务的简介
3.1.1、什么是事务?

数据库的事务是指一组SQL语句组成的数据库逻辑处理单元,在这组SQL的操作中,要么全部执行成功,要么全部执行失败。
这里的一组SQL操作,举个简单又经典的例子就是转账了,事务A中要进行转账,那么转出的账号要扣钱,转入的账号要加钱,这两个操作都必须同时执行成功,为了确保数据的一致性。

3.1.2、为什么需要事务?

数据库数据是有缓存的,如果没有缓存,每次都从物理磁盘读写数据,那性能就太低下了:

  • 数据库中存放的数据文件称为 data file ;日志文件称为log file
  • 缓存中存放的数据称为 data buffer ;存放的日志称为 log buffer

既然数据库数据有缓存,就要保证缓存数据与磁盘数据的一致性:

  • 从缓存同步到磁盘的过程中一旦发生服务宕机或断电,缓存数据会丢失,缓存与磁盘数据出现不一致问题;
  • 在并发环境下两个线程操作同一个块数据,如果没有处理好一致性问题,那么“线程1”读到缓存的数据可能是“线程2”的缓存;

缓存存在的意义是为了提供更好的查询性能,如果连数据一致性都无法保证那么毫无意义!

这也是使用事务的最终目的:是为了保证数据的一致性!

3.1.3、事务适用场景

常用事务处理操作量大,复杂度高的数据,效率高又省时:

  • 比如人员管理系统中,删除一个人员,既需要删除人员的基本资料,也要删除和该人员相关的信息:如信箱、文章等等;这些数据库操作语句就构成一个事务;
  • 在 批量 操作时使用事务效果会比较明显。比如一次插入100条记录;
3.1.4、事务的特性

一般来说,事务具有四大特性:

  • 原子性:事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败;实现事务的原子性,是基于日志的 Redo/Undo 机制。
    事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性:执行事务前后的状态要一致,必须使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
    这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  • 隔离性:并发执行的事务不会相互干扰,其对数据库的影响和它们串行执行时一样;隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致;
    比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。
    事务隔离分为不同级别,包括读未提交、读提交、可重复读和串行化;多个事务存在的情况,通过锁和MVCC多版本控制来管理好事务间的隔离关系!
  • 持久性:指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的;任何事务或系统故障都不会导致数据丢失。

原子性、隔离性、持久性都是为了保障一致性而存在的,一致性也是最终的目的。

Redo/Undo 机制:

  • Redo log 用来记录某数据块被修改后的值,可以用来恢复未写入 data file 的已成功事务更新的数据;
  • Undo log 用来记录数据更新前的值,保证数据更新失败能够回滚。

假如数据库在执行的过程中,不小心崩了,可以通过该日志的方式,回滚之前已经执行成功的操作,实现事务的一致性。

3.2、SQLite中使用事务
3.2.1、事务的相关命令

SQLite目前不允许嵌套事务。使用下面的命令来控制事务:

  • BEGINSTART TRANSACTION 显式开启一个事务;这样启动的事务会在下一条COMMITROLLBACK命令之前一直有效。但若数据库关闭或出现错误且选用ROLLBACK冲突判定算法时,数据库也会ROLLBACK
  • COMMIT 等价于 COMMIT WORK ,提交事务,对数据库所有的修改是永久性的;
  • ROLLBACK 等价于 ROLLBACK WORK 回滚COMMIT之前的事务,并撤销正在进行的所有未提交的修改;
  • SAVEPOINT identifier 在事务中创建一个保存点,一个事务中可以有多个保存点;
  • RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
  • ROLLBACK TO identifier把事务回滚到标记点;
3.2.2、事务的分类

在SQLite 3.0.8 或更高版本中,事务可以是延迟的、即时的或者互斥的。

  • 延迟事务:在数据库第一次被访问之前不获得锁,这样就会延迟事务,BEGIN语句本身不做任何事情;
    直到初次读取或访问数据库时才获取锁,对数据库的初次读取创建一个SHARED锁,初次写入创建一个RESERVED锁。
    由于锁的获取被延迟到第一次需要时,别的线程可以在当前线程执行BEGIN语句之后创建另外的事务写入数据库。
  • 即时事务:执行BEGIN命令后立即获取RESERVED锁,而不等数据库被使用。在执行BEGIN IMMEDIATE之后,其它线程不能写入数据库或执行BEGIN IMMEDIATEBEGIN EXCLUSIVE,但可以读取数据库。
  • 互斥事务:在所有的数据库获取EXCLUSIVE锁,在执行BEGIN EXCLUSIVE之后,在当前事务结束前其它线程不能够读写数据库。
3.2.3、事务的回滚

重做日志 redo log
作用:确保事务的持久性!防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
内容:物理格式的日志,记录的是事务开始执行修改后的数据,其redo log是顺序写入redo log file的物理文件中去的。
什么时候产生:事务开始之后就产生redo log,redo log的落盘并不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入redo log文件中。
什么时候释放:当对应事务的脏页写入到磁盘之后,redo log的使命也就完成了,重做日志占用的空间就可以重用(被覆盖)。

回滚日志undo log记录了上次 -commit到现在的所有数据改变,通过undo log,回滚可以把数据库恢复到上次 -commit-rollback 命令时的情况!
同时undo log可以提供并发控制下的读取数据,即非锁定读。

回滚日志undo log
内容:逻辑格式的日志,在执行回滚时将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,这一点是不同于redo log的。
什么时候产生:事务开始之前,将当前是的版本生成undo log,回滚也会产生 redo log 来保证undo log的可靠性;
什么时候释放:当事务-commit之后,undo log并不能立马被删除,而是放入待清理的链表,由purge线程判断是否由其他事务在使用undo段中表的上一个事务之前的版本信息,决定是否可以清理undo log的日志空间

3.2.4、事务的保存点

SavePoint 就是事务中的一点,当事务回滚时可以回滚到指定的 SavePoint;用于取消部分事务,而不是回滚到 -commit 之前!当结束事务时,会自动的删除该事务中所定义的所有保存点。
通过 SavePoint 可以在数据库事务处理中实现嵌套事务的方法;SavePoint的个数没有限制!

3.2.5、事务的检查点

修改数据时,需要将数据读入内存 Buffer Cache 中,并记录重做日志 Redo 用于数据库崩溃之后可以恢复。因为重做日志Redo的存在,不需要在提交数据时立即将变化的数据写回磁盘(立即写的效率会很低)。

最常见的情况,数据库因断电而Crash,那么内存中修改过的、尚未写入文件的数据将会丢失 ;在下一次数据库启动之后,可以通过 Redo 对事务前滚,将数据库恢复到崩溃之前的状态;然后数据库打开,将未提交的数据进行回滚。

在这个过程中,关键在于数据库要经历多久才能打开;也就是需要读取多少重做日志才能完成前滚?这个时间越短越好!

检查点 Checkpoint 只是一个数据库事件,它的存在是为了缩短这个崩溃恢复时间。当检查点发生时,Oracle会通知 DBWR 进程,把修改过的数据,也就是Checkpoint SCN之前的脏数据从Buffer Cache写入磁盘,当写入完成之后,CKPT进程更新控制文件和数据文件头,记录检查点信息,标识变更。

4、SQLite 的C/C++ API接口

SQLite 由两个关键对象以及相关个函数构成了 C/C++ API接口的基本元素:

  • sqlite3 : 每个打开的SQLite数据库都由一个指向结构 sqlite3 实例的指针表示;
  • sqlite3_stmt : 预处理语句,将原始的 SQL 文本编译为二进制数据,可以直接进行计算;
4.1、 数据库对象 sqlite3
/** 数据库连接 Handle
 * 每个打开的SQLite数据库都由一个指向结构 sqlite3 实例的指针表示;可以将sqlite3指针看作一个对象。
 *
 * 构造函数 : sqlite3_open(), sqlite3_open16(), 和 sqlite3_open_v2();
 * 析构函数 : sqlite3_close() 和 sqlite3_close_v2() ;
 * sqlite3对象还有许多其他函数如 : sqlite3_prepare_v2()、sqlite3_create_function() 和 sqlite3_busy_timeout()
 */
typedef struct sqlite3 sqlite3;
4.1.1、 数据库 sqlite3 的构造与析构
/** sqlite3 的构造函数:根据指定路径打开一个 SQLite 数据库,如果数据库不存在就创建它
 * @param filename 文件名,UTF-8编码的数据库名称!
 *          如果是 NULL 或 ':memory:',那么该函数将会在 RAM 中创建一个内存数据库,这只会在 session 的有效时间内持续。
 *          如果文件名不为 NULL,那么该函数将使用这个参数值尝试打开数据库文件。
 *          如果该名称的文件不存在,将创建一个新的命名为该名称的数据库文件并打开。
 * @param ppDb 数据库的连接对象
 * @param zVfs 虚拟文件系统 VFS 名称
 * @param flags 取下述三个值:
 *      SQLITE_OPEN_READONLY : 数据库只读,如果数据库不存在,则返回一个错误;
 *      SQLITE_OPEN_READWRITE 打开数据库进行读写操作,或者文件被读保护的情况下仅进行写操作。此时数据库必须存在,否则将返回一个错误。
 *      SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE 类似于 sqlite3_open()函数:打开数据库进行读写操作,如果数据库不存在就创建它。
 *
 * flags 可以选择与 SQLITE_OPEN_NOMUTEX、SQLITE_OPEN_FULLMUTEX 、SQLITE_OPEN_SHAREDCACHE 、SQLITE_OPEN_PRIVATECACHE 或 SQLITE_OPEN_URI 组合:
 *
 */
int sqlite3_open(const char *filename,sqlite3 **ppDb);
int sqlite3_open16(const void *filename,sqlite3 **ppDb); 
int sqlite3_open_v2(const char *filename,sqlite3 **ppDb,int flags,const char *zVfs);

/** 是 sqlite3 的析构函数 :关闭数据库连接
 *
 * @param sqlite3* 只能是没有关闭的 sqlite3 对象 或者 NULL, 但 NULL 没有任何意义;
 * @return 如果 sqlite3 对象被成功销毁,并且所有相关资源被释放,返回 SQLITE_OK ;
 *
 * @note 如果 sqlite3 对象在事务打开时被销毁,事务将自动回滚;
 * @note 理想情况下,应用程序应该由 sqlite3_finalize() 释放 sqlite3_stmt ;sqlite3_blob_close() 关闭 BLOB句柄,以及 sqlite3_backup_finish() 完成所有与 sqlite3_backup 对象关联的 sqlite3_backup对象,然后再尝试关闭该 sqlite3 对象。
 * @note 如果存在未释放的 sqlite3_stmt 、未关闭的 BLOB handlers、或未完成的 sqlite3_backup 对象:
 *     那么调用 sqlite3_close() 将保持数据库连接打开并返回 SQLITE_BUSY ;
 *     那么调用 sqlite3_close_v2() 将返回 SQLITE_OK 但不会立即释放数据库连接 ,此时sqlite3 对象被标记为不可用的僵尸对象,只有上述工作都完毕,才会释放 sqlite3 对象!
 */
int sqlite3_close(sqlite3*);
int sqlite3_close_v2(sqlite3*);
4.1.2、 配置数据库
/** 配置 SQLite 库
 * @param 第一个参数是配置选项,它决定要配置SQLite的哪个属性;后续参数取决于第一个参数中的[configuration option]。
 * @return 设置成功返回SQLITE_OK ,失败返会非零错误码;
 *
 * 该函数更改 SQLite 全局配置,以便将SQLite调优到应用程序的特定需求;大多数程序都使用默认配置不需要这个函数。
 * 该函数不是线程安全的,使用时,应用程序必须确保其它线程不会调用其它 SQLite 函数。
 *
 * 该函数只能在库使用 sqlite3_initialize() 初始化之前、或者使用 sqlite3_shutdown()关闭之后调用;如果在 sqlite3_initialize() 之后和 sqlite3_shutdown() 之前调用,那么它将返回错误码SQLITE_MISUSE。
 */
int sqlite3_config(int, ...);
4.1.3、 中断数据库 sqlite3 正在执行的 Sql
/** 中断当前正在执行的 Sql 语句,
 * @note 那些被中断的SQL操作将返回错误码 SQLITE_INTERRUPT
 * @note 如果没有正在执行的 Sql 语句,调用该函数是无意义的;调用该函数之后,再次执行Sql 语句不受任何影响!
 * @note 如果中断的SQL操作是显式事务中的 INSERT、UPDATE、 DELETE操作,那么整个事务将自动回滚。
 * @note 该函数是线程安全的,但需要确保在调用该函数值前数据库是打开的!如果SQL语句在调用sqlite3_interrupt()时同时完成,那么它可能没有机会被中断将继续完成。
 *
 * 在sqlite3_interrupt()调用之后以及运行的语句计数达到零之前启动的任何新SQL语句都将被中断,就好像它们在sqlite3_interrupt()调用之前已经运行过一样。
 * 在运行的语句计数达到零之后启动的新SQL语句不受sqlite3_interrupt()的影响。
 */ 
void sqlite3_interrupt(sqlite3*);
4.1.4、某些修改信息
/**  最后插入的 Rowid
*
* @rowid 64位有符号整数键,大部分 SQLite 表中都有个唯一的 rowid(除非显示声明rowid导致它不可用);rowid列的另一个别名是表中有类型为 INTEGER PRIMARY KEY 的列。
* @return 返回最后 INSERT 成功的一行的 rowid;如果没有成功 INSERT 到表中,那么返回零;INSERT 到没有 rowid 的表,则不被记录!
* @note 除了 INSERT 时自动设置,该函数返回值也可以由 sqlite3_set_last_insert_rowid() 显式设置;
*
* 一些 virtual table 可能会将 INSERT 操作作为提交事务的一部分(例如,将内存中积累的数据同步到磁盘)。在这种情况下,调用该函数将返回与这些内部插入操作相关的 rowid,这会导致不直观的结果。在返回给用户之前使用 sqlite3_set_last_insert_rowid() 恢复原始rowid值可以避免这个问题!
*
* 如果在 trigger 中出现 INSERT,只要 trigger 在运行,该函数就会返回所插入行的 rowid。trigger 运行结束,该函数返回的值就会恢复到trigger 运行之前的值!
*
* 因违反约束而 INSERT 失败、回滚操作、中止操作,不会改变该函数返回值。
*
* SQL语句可以通过 last_insert_rowid() 访问该函数;如果在不同线程执行 INSERT,则该函数返回结果是不可预测的。
*/ 
sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);

/** 设置最后Insert一行的Rowid
* 直接改变 Rowid ,而不是不向数据库插入一行。
*/
void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64);

/** 前一个 SQL 语句更改了多少行
* @return 返回指定数据库上最近完成的INSERT、UPDATE或DELETE命令的行数;仅仅针对表,对于 view 不做计算!
*         执行任何其他类型的SQL语句,如 CREATE、REPLACE、triggers  都不会修改这个函数返回的值。
*
*
* 如果在 trigger 运行时执行sqlite3_changes()函数,事情就会变得更加复杂。如果程序使用[changes() SQL函数],或者其他一些回调函数直接调用sqlite3_changes(),就会发生这种情况:
*   
  • 在输入trigger之前,将保存sqlite3_changes()函数返回的值。trigger 程序完成后,恢复原始值 *
  • 在 trigger 程序中,每条INSERT、UPDATE和DELETE语句都会设置sqlite3_changes()在正常情况下完成时返回的值。 * 当然,这个值不包括由子触发器执行的任何更改,因为sqlite3_changes()值将在每个子触发器运行后保存并恢复 * * 这意味着,如果 trigger 中的第一个INSERT、UPDATE或DELETE语句使用了changes() 函数,它将返回调用语句开始执行时设置的值。如果 trigger 中的第二个或后续语句使用它,则返回的值反映同一 trigger 中前一个INSERT、UPDATE或DELETE语句修改的行数。 * * @note 该函数不是线程安全的,如果在调用该函数的同时,对数据库作了修改,那么返回值不可预测! */ int sqlite3_changes(sqlite3*); /** 修改的总行数 * @return 返回自打开数据库以来已经完成的所有 INSERT、UPDATE 或 DELETE 命令的总行数,包括作为 trigger 程序的执行的一部分行数。 * 执行任何其他类型的SQL语句都不会影响该函数返回值。 * 作为外键操作的部分更改包括在计数中,但作为替换约束解析的部分更改不包括在内。 * * @note 该函数不是线程安全的,如果在调用该函数的同时,对数据库作了修改,那么返回值不可预测! */ int sqlite3_total_changes(sqlite3*);
  • 4.1.5、 注册一个回调函数来处理 SQLITE_BUSY 错误
    /** 注册一个回调函数来处理 SQLITE_BUSY 错误
     *
     * @param sqlite3* 指定的数据库
     * @param int(*)(void*,int) 回调函数,默认为NULL,意味着在遇到锁事件时给应用程序立即返回 SQLITE_BUSY;
     *                          如果注册了回调函数,则在遇到锁事件时可能调用回调函数,也可能返回 SQLITE_BUSY;
     * 如果SQLite确定调用注册的回调函数会导致死锁,那么SQLite 将继续执行并将 SQLITE_BUSY 返回给应用程序,而非调用回调函数!
     *
     * 对于回调函数的参数与返回值:
     *   第一个参数是 sqlite3_busy_handler() 的第三个参数的拷贝副本,
     *   第二个参数 int 代表本次锁事件当中,该回调函数被调用次数;
     *   返回值:如果返回为0,不会再次访问数据库,将 SQLITE_BUSY 返回给应用程序;
     *         如果返回非0,将再次尝试访问数据库,重复这个循环!
     *
     *
     * @note 每个数据库仅能使用 sqlite3_busy_handler() 注册一个回调函数!再次调用  sqlite3_busy_handler(),则之前的回调函数会失效!可以调用 sqlite3_busy_timeout() 更改回调函数,从而清除以前设置的任何回调函数
     * @note 回调函数不能关闭数据库或预处理语句;
     *
     * @abstract 程序运行过程中,如果有其它线程在读写数据库,那么注册的回调函数会不断被调用,直到其他线程释放锁;获得锁之后,不会再调用回调函数,从而向下执行,进行数据库操作:
     * @case 考虑这样一种情况:一个线程持有一个读锁,它试图将这个读锁提升为一个保留锁,而另一个线程持有一个保留锁,它试图将这个保留锁提升为一个独占锁。
     *   第一个线程不能继续,因为它被第二个线程阻止了;第二个线程不能继续,因为它被第一个线程阻止了。
     *   如果两个线程都调用该函数,那么它们都不会取得任何进展。
     *   因此,SQLite为第一个线程返回 SQLITE_BUSY,希望这会导致第一个线程释放其读锁,并允许第二个线程继续。
     */ 
    int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
    
    /** 设置表被 lock 时 sqlite3_busy_handler() 的休眠时间
     * @param sqlite3* 指定的数据库
     * @param ms 休眠多少毫秒; <= 0的时间将使得 sqlite3_busy_handler() 无意义!
     *
     * @abstract sqlite3_busy_handler() 将多次休眠,直到积累了 ms 毫秒之后返回 0,并且对应的函数返回 SQLITE_BUSY;
     */
    int sqlite3_busy_timeout(sqlite3*, int ms);
    
    4.2、预处理语句 sqlite3_stmt

    sqlite3_stmt 并不是我们所熟悉的 SQL 文本,如果将每个 SQL 语句看作一个单独的计算机程序,那么原始的 SQL 文本是源代码;sqlite3_stmt 对象是编译后的对象代码,用 Sqlite3 标记记录的内部数据结构以二进制形式存在,可以直接进行计算。

    /** 预处理语句 sqlite3_stmt */
    typedef struct sqlite3_stmt sqlite3_stmt;
    

    所有 SQL 语句在运行之前都必须转换成预处理语句;预处理语句对象的生命周期通常是这样的:

    • <1> 调用 sqlite3_prepare_v2() 创建预处理语句
    • <2> 调用 sqlite3_bind_*() 系列函数将占位值绑定到对应参数
    • <3> 一次或多次调用 sqlite3_step() 函数运行SQL
    • <4> 调用 sqlite3_reset() 函数重置预处理语句,然后返回到步骤3;这样可以缓存 sqlite3_stmt 并在使用的时候先重置再使用;
    • <5> 调用 sqlite3_finalize() 函数销毁对象
    4.2.1、 预处理语句 sqlite3_stmt 的构造与析构
    /** sqlite3_stmt 的构造函数,将SQL文本编译为字节代码,它将完成查询或更新数据库的工作。
     * @param db 数据库
     * @param zSql UTF-8编码的SQL语句
     * @param nByte  SQL的最大字节长度
     * @param ppStmt 预处理语句 sqlite3_stmt 的指针地址
     */
    int sqlite3_prepare(sqlite3 *db,const char *zSql,int nByte,sqlite3_stmt **ppStmt,const char **pzTail);
    int sqlite3_prepare_v2(sqlite3 *db,const char *zSql,int nByte,sqlite3_stmt **ppStmt,const char **pzTail);
    
    /** sqlite3_stmt 的析构函数
     */
    int sqlite3_finalize(sqlite3_stmt *pStmt);
    
    4.2.2、 将 Sql 中的占位值绑定到预处理语句 sqlite3_stmt
    /** 将应用程序数据存储到 原始SQL的参数中
     */
    int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
    int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64,void(*)(void*));
    int sqlite3_bind_double(sqlite3_stmt*, int, double);
    int sqlite3_bind_int(sqlite3_stmt*, int, int);
    int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
    int sqlite3_bind_null(sqlite3_stmt*, int);
    int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*));
    int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*));
    int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64,void(*)(void*), unsigned char encoding);
    int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
    int sqlite3_bind_pointer(sqlite3_stmt*, int, void*, const char*,void(*)(void*));
    int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
    int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64);
    
    
    /** 一条 sql 语句可能有多个 SQL 命令: 该函数解析并执行由 sql 参数所给的每个命令,直到字符串结束或者遇到错误为止。
     * 该函数对一个或多个SQL 命令字符串执行 sqlite3_prepare(), sqlite3_step(), sqlite3_column()和 sqlite3_finalize()。
     * @param sql 待执行的sql
     * @param callback 回调函数
     * @param void * 回调函数的第一个参数
     * @param errmsg 错误信息
     */
    int sqlite3_exec(sqlite3*,const char *sql,int (*callback)(void*,int,char**,char**),void *,char **errmsg);
    
    /** 获取 SQLite 库的版本
     */
    const char *sqlite3_libversion(void);
    const char *sqlite3_sourceid(void);
    int sqlite3_libversion_number(void);
    
    int sqlite3_column_count(sqlite3_stmt *pStmt);
     
    const char *sqlite3_column_name(sqlite3_stmt*, int N);
    const void *sqlite3_column_name16(sqlite3_stmt*, int N);
    const char *sqlite3_column_database_name(sqlite3_stmt*,int);
    const void *sqlite3_column_database_name16(sqlite3_stmt*,int);
    const char *sqlite3_column_table_name(sqlite3_stmt*,int);
    const void *sqlite3_column_table_name16(sqlite3_stmt*,int);
    const char *sqlite3_column_origin_name(sqlite3_stmt*,int);
    const void *sqlite3_column_origin_name16(sqlite3_stmt*,int);
    const char *sqlite3_column_decltype(sqlite3_stmt*,int);
    const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
    const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
    double sqlite3_column_double(sqlite3_stmt*, int iCol);
    int sqlite3_column_int(sqlite3_stmt*, int iCol);
    sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
    const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
    const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);
    sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
    int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
    int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
    int sqlite3_column_type(sqlite3_stmt*, int iCol);
    
    4.2.3、 执行预处理语句 sqlite3_stmt
    /** 将 sqlite3_stmt 前进到下一个结果或完成
     */
    int sqlite3_step(sqlite3_stmt*);
    

    4.3 、SQLite函数常用错误码

    /** SQLite函数返回结果码定义 */
    
    #define SQLITE_OK           0   /* 成功的结果 */
    /* 错误码 */
    #define SQLITE_ERROR        1   /* 通用错误 */
    #define SQLITE_INTERNAL     2   /* SQLite中的内部逻辑错误 */
    #define SQLITE_PERM         3   /* 没有权限访问 */
    #define SQLITE_ABORT        4   /* 回调请求中止 */
    #define SQLITE_BUSY         5   /* 数据库文件被锁定 */
    #define SQLITE_LOCKED       6   /* 数据库中的表被锁定 */
    #define SQLITE_NOMEM        7   /* malloc()失败 */
    #define SQLITE_READONLY     8   /* 尝试编写一个只有读取权限的数据库 */
    #define SQLITE_INTERRUPT    9   /* 被 sqlite3_interrupt() 终止操作*/
    #define SQLITE_IOERR       10   /* 一些磁盘I/O错误 */
    #define SQLITE_CORRUPT     11   /* 数据库磁盘映像格式错误 */
    #define SQLITE_NOTFOUND    12   /* sqlite3_file_control() 中的未知错误 */
    #define SQLITE_FULL        13   /* 插入失败,因为数据库已满 */
    #define SQLITE_CANTOPEN    14   /* 无法打开数据库文件 */
    #define SQLITE_PROTOCOL    15   /* 数据库 lock 协议错误 */
    #define SQLITE_EMPTY       16   /* 仅限内部使用 */
    #define SQLITE_SCHEMA      17   /* 数据库 schema 改变 */
    #define SQLITE_TOOBIG      18   /* 字符串或BLOB超出大小限制 */
    #define SQLITE_CONSTRAINT  19   /* 因违反约束而中止 */
    #define SQLITE_MISMATCH    20   /* 数据类型不匹配 */
    #define SQLITE_MISUSE      21   /* 库使用不当 */
    #define SQLITE_NOLFS       22   /* 系统不支持 */
    #define SQLITE_AUTH        23   /* 授权被拒 */
    #define SQLITE_FORMAT      24   /* Not used */
    #define SQLITE_RANGE       25   /* sqlite3_bind 的第二个参数超出范围 */
    #define SQLITE_NOTADB      26   /* 打开的文件不是数据库文件 */
    #define SQLITE_NOTICE      27   /* sqlite3_log() 通知 */
    #define SQLITE_WARNING     28   /* sqlite3_log() 警告 */
    #define SQLITE_ROW         100  /* sqlite3_step() 执行另一行 */
    #define SQLITE_DONE        101  /* sqlite3_step() 完成执行 */
    

    Demo


    参考文章:
    MySQL的事务

    你可能感兴趣的:(iOS之SQLite3)