深入浅出SQLITE3

文章目录

  • 前言
  • 引言
  • 体系结构
    • 接口(interface)
    • 编译器(Compiler)
    • 虚拟机
    • 后端(back-end)
  • SQLite数据库特点
  • 入门
    • SQLite CLP模式种.schema和.tables
    • 数据导出
    • 数据导入
    • 格式化
  • 关系模型
  • SQL语句
    • 创建表
    • 修改表
    • 函数
    • 聚合
    • 分组(Grouping)
    • 去重
    • 多表连接
    • 插入数据
    • 修改数据
    • 删除数据
  • 实体完整性
    • 唯一约束
    • 主键约束
  • 域完整性
    • 默认值
    • 排序法
    • 存储类
    • 事务
    • 事物的范围
    • 冲突解决
    • 数据库锁
    • 死锁
    • 事物种类


前言

这里是阅读的网名为空转大大翻译的SQLITE3书籍翻译的数据是英文版的《The Definitive Guide to SQLite》。因为去看原版英文肯定是要花不少的时间的,所以选择看该作者翻译的书籍,收获颇丰,也希望下面的内容对大家有所帮助。

引言

The author disclaims copyright to this source code. 
In place of a legal notice, here is a blessing:
May you do good and not evil.
May you find forgiveness for yourself and forgive others.
May you share freely, never taking more than you give.

这是SQLITE源码的第一段英文,感觉对于一个热爱计算机编程的人来讲非常的有使命感和责任感,希望大家能够共勉

废话不多说直接上干货

体系结构

对于我个人而言,学习一门新的技术栈,最重要的其实不是其使用方法而是其体系结构和编译原理
SQLite拥有一个精致的、模块化的体系结构,并引进了一些独特的方法进行关系型数据库的管理。它由被组织在3个子系统中的8个独立模块组成,如下图。这个模型将查询过程划分为几个不连续的任务,就像在流水线上工作一样。在体系结构栈的顶部编译查询语句,在中部执行它,在底部处理操作系统的存储和接口
深入浅出SQLITE3_第1张图片

接口(interface)

接口由SQLite C API组成,也就是说不管是程序、脚本语言还是库文件,最终都是通过调用它(C API)与SQLite交互的。

编译器(Compiler)

编译过程从分词器(Tokenizer)和分析器(Parser)开始。它们协作处理文本形式的结构化查询(Structured Query Language,SQL)语句,分析其语法有效性,转化为底层能更方便处理的层次数据结构——语法树,然后把语法树传给代码生成器(code generator)进行处理。SQLite分词器的代码是手工编写的,分析器代码由SQLite定制的分析器生成器称为(Lemon)生成的。一旦SQL语句被分解为串值并组织到语法树中,分析器就将该树下传给代码生成器进行处理。而代码生成器根据它生成一种SQLite专用的汇编代码,最后由虚拟机(Virtual Machine)执行。

虚拟机

架构中最核心的部分就是虚拟机,或者叫虚拟数据库引擎(Virtual DataBase Engine,VDBE)。它和Java虚拟机相似,解释执行字节代码。VDBE的字节代码(称为虚拟机语言)由128个操作码构成。主要是进行数据库操作。它的每一条指令或者用来完成特定的数据库操作(比如打开一个表的游标,开始一个事物等),或者为完成这些操作做准备。总之,所有这些指令都是为了满足SQL命令的要求。VDBE的指令集能满足任何复杂SQL命令的要求。

后端(back-end)

后端由B-tree、页缓冲(page cache,pager)和操作系统系统(系统调用)构成。B-tree和page cache共同对数据进行管理。它们操作的是数据库页,这些页具有相同的大小,就像一个集装箱。页里面的“货物”是表示信息的大量bit,这些信息包括记录、字段和索引入口等。B-tree和pager都不知道信息的具体内容,它们只负责“运输”这些页,页不关心这些“集装箱”里面是什么。
B-tree的主要功能就是索引,它维护着各个页之间的复杂关系,便于快速找到所需数据。它把组织成树型的结构(这是它名称的由来),这种树是为查询而高度优化了的。Page为B-tree服务,为他提供页。Pager的主要作用就是通过OS接口在B-tree和磁盘之间传递页
磁盘操作是计算机到目前为止所必须做的最慢的事情。所以,pager尽力提高速度,其方法是把经常使用的页存放到内存当中的也缓冲区里,从而尽量减少操作磁盘的次数。他使用特殊的算法来预测下面要使用那些页,从而使B-tree能够更快地工作。

SQLite数据库特点

零配置:就是不需要DBA(数据库管理员)
简单:使用它只需要引入头文件和源C文件就行
紧凑性:一共不超过240KB
兼容性:支持绝大多数的操作系统
没有限制:开源,完全免费
适应性:作为内嵌形数据库,有强有力可伸缩的关系型数据库前端,和紧凑的B-tree后端。

关系型数据库与非关系型数据库的区别:大家自己百度,一定要区分清除。其实主要就是存储数据结构的方式不同

入门

源码下载地址

SQLite CLP模式种.schema和.tables

.tables[pattern]:返回所有符合条件的视图和表
.schema[table name]:得到一个表或视图定义的DDL语句

数据导出

.dump命令将数据库导出SQL格式的文件。不带参数表示将整个数据库导出。如果提供参数,CLP把参数理解为表名或视图名。

数据导入

有两种方法导入数据,用那种方法决定于要导入的文件格式。如果文件由SQL
语句构成,可以使用.read命令直接导入文件。
如果文件是由逗号或其他界符分割的值(CSV)组成,可以使用.import[file][table]命令。此命令将解析指定的文件并尝试将数据插入到指定的表中。

格式化

CLP提供了几个格式化选项命令。最简单的是.echo,如果设置了.echo on,则新输入的命令在执行前都会回显,默认值是off。.headers设置为on时,查询结果显示时带有字段名。当遇到NULL值时,如果需要以一个字符串来显示,使用.nullvalue命令设置。
如果要改变CLP的shell提示符,使用.prompt[value]
.mode命令可以设置结果数据的几种输出格式。可选的格式为csv、column、html、insert、line、list、tabs和tcl。默认值是list。在此模式下显示结果实例以默认的分隔符进行分割。
可通过.separator命令进行设置。
可通过dbname VCUUM来释放数据库中未被使用的空间。

关系模型

详解关系模型
关系型与非关系型模型区别
SQLITE极限
深入浅出SQLITE3_第2张图片table->tuples(行)->arrtibutes(列)

在SQL中,创建和删除数据库对象的语句一般被称为数据定义语言(data definition language,DDL),操作这些对象中数据的语句称为数据操作语言(data manipulation language,DML)。创建表的语句属于DDL

SQL语句

创建表

CREATE TABLE contacts ( id INTEGER PRIMARY KEY,
name TEXT NOT NULL COLLATE NOCASE,
phone TEXT NOT NULL DEFAULT ‘UNKNOWN’,
UNIQUE (name,phone) );

修改表

ALTER TABLE table { RENAME TO name | ADD COLUMN column_def }

函数

SQLite 提供了多种内置的函数和聚合,可以用在不同的子句中。函数的种类包括:数学函数,如 ABS()计算绝对值;字符串格式函数,如 UPPER()和 LOWER(),它们将字符串的值转化为大写或小写。函数名是不分大小写的(或 upper()和 UPPER()是同一个函数)。

聚合

聚合是一类特殊的函数,它从一组记录中计算聚合值。标准的聚合函数包括 SUM()、AVG()、COUNT()、MIN()和 MAX()。

分组(Grouping)

聚合的精华部分是分组。聚合不只是能够计算整个结果集的聚合值,你还可以把结果集分成多个组,然后计算每个组的聚合值。这些都可以在一步当中完成,方法就是使用 GROUP BY子句。

去重

操作管道中的下一个限制是 DISTINCT。DISTINCT 处理 SELECT 的结果并过滤掉其中重复的行。

多表连接

连接(join)是 SELECT 命令的第一个操作,它产生初始的信息,供语句的其它部分过滤和处
理。连接的结果是一个合成的关系(或表),它是 SELECT 后继操作的输入。

插入数据

INSERT INTO table (column_list) VALUES (value_list);

修改数据

UPDATE table SET update_list WHERE predicate;

删除数据

DELETE FROM table WHERE predicate;

实体完整性

唯一约束

UNIQUE

UNIQUE与NULL的关系:
问题:如果一个字段已经声明为UNIQUE,可以向这个字段插入多少NULL值?
SQLite采用了与PostgreSQL和 Oracle 相同的解决方案。,可以插入多个NULL

问题2:两个NULL值是否相等
你没有足够的信息来证明它们相等,但也没有足够的信息证明它们不等。SQLite 的观点是假设所有的 NULL都是不同的。所以你可以向唯一字段中插入任意多个 NULL 值。

主键约束

在 SQLite 中,当你定义一个表时总要确定一个主键,不管你自己有没有定义。这个字段是一个 64-bit 整型字段,称为 ROWID。它还有两个别名——_ROWID_和 OID,用这两个别名同样可以取到它的值。它的默认取值按照增序自动生成。SQLite 为主键字段提供自动增长特性。

域完整性

默认值

保留字 DEFAULT 为字段提供一个默认值。如果用 INSERT 语句插入记录时没有为该定做指定值,则为它赋默认值。DEFAULT 不是一个约束(constraint),因为它没有强制任何事情。这所以把它归为域完整性,是因为它提供了处理 NULL 值的一个策略。如果一个字段没有指定默认址,在插入时也没有为该字段指定值,SQLite 将向该字段插入一个 NULL。

排序法

排序法定义如何唯一地确定文本的值。排序法主要用于规定文本值如何进行比较。不同的排序法有不同的比较方法。例如,某种排序法是大小写不敏感的,于是'JujyFruit'和'JUJYFRUIT'被认为是相等的。另外一个排序法或许是大小写敏感的,这时上面两个字符串就不相等了。SQLite 有 3 种内置的排序法。默认为 BINARY,它使用一个 C 函数 memcmp()来对文本进行逐字节的比较。

存储类

深入浅出SQLITE3_第3张图片

事务

事务定义了一组SQL命令的边界,这组命令或者作为一个整体被全部执行,或者都不执行。
典型代表就是转账

事物的范围

事物由3个命令控制:BEGIN、COMMIT和ROLLBACK。BEIGIN开始一个事物,之后所有操作都可以取消。COMMIT使BEGIN后的命令 得到确认;而ROLLBACK还原BEGIN之后的所有操作。
SQLite默认情况下,每条SQL语句自成事物(自动提交模式)

冲突解决

当违反约束导致事物的非法结束时,大多数数据库(管理系统)都是简单地将前面所做地修改全部取消掉。
SQLite有其独特的方法来处理约束违反(或说从约束违反中恢复),称为冲突解决。

数据库锁

SQLite 采用粗放型的锁。当一个连接要写数据库,所有其它的连接被锁住,直到写连接结束了它的事务。SQLite 有一个加锁表,来帮助不同的写数据库都能够在最后一刻再加锁,以保证最大的并发性。

SQLite 使用锁逐步上升机制,为了写数据库,连接需要逐级地获得排它锁。SQLite 有 5 个
不同的锁状态:未加锁(UNLOCKED)、共享(SHARED)、保留(RESERVED)、未决(PENDING)和排它(EXCLUSIVE)。每个数据库连接在同一时刻只能处于其中一个状态。每种状态(未加锁状态除外)都有一种锁与之对应。

最初的状态是未加锁状态,在此状态下,连接还没有存取数据库。当连接到了一个数据库,甚至已经用 BEGIN 开始了一个事务时,连接都还处于未加锁状态。
未加锁状态的下一个状态是共享状态。为了能够从数据库中读(不写)数据,连接必须首先进入共享状态,也就是说首先要获得一个共享锁。多个连接可以同时获得并保持共享锁,也就是说多个连接可以同时从同一个数据库中读数据。但哪怕只有一个共享锁还没有释放,也不允许任何连接写数据库。如果一个连接想要写数据库,它必须首先获得一个保留锁。一个数据库上同时只能有一个保留锁保留锁可以与共享锁共存,保留锁是写数据库的第1 阶段保留锁即不阻止其它拥有共享锁的连接继续读数据库,也不阻止其它连接获得新的共享锁
一旦一个连接获得了保留锁,它就可以开始处理数据库修改操作了,尽管这些修改只能在缓冲区中进行,而不是实际地写到磁盘。对读出内容所做的修改保存在内存缓冲区中。当连接想要提交修改(或事务)时,需要将保留锁提升为排它锁。为了得到排它锁,还必须首先将保留锁提升为未决锁。获得未决锁之后,其它连接就不能再获得新的共享锁了,但已经拥有共享锁的连接仍然可以继续正常读数据库。此时,拥有未决锁的连接等待其它拥有共享
锁的连接完成工作并释放其共享锁。一旦所有其它共享锁都被释放,拥有未决锁的连接就可以将其锁提升至排它锁,此时就可以自由地对数据库进行修改了。所有以前对缓冲区所做的修改都会被写到数据库文件.

死锁

深入浅出SQLITE3_第4张图片
如上图,A连接到数据库,处于未加锁状态,B也连接到数据库,也处于未加锁状态,当A进行select语句时,读取数据库,此时A获得一个共享锁,而B因为需要对数据库进行写操作,因此在A已经处于共享锁状态时,所以B先处于保留锁状态,对缓冲区进行操作,但是B执行了COMMIT操作,B将保留锁提升到未决锁状态,阻隔其他新的连接得到共享锁,在提升到排他锁,发现A未释放掉共享锁状态,依旧在读取数据库,因此无法升级为排他锁,所以无法写数据库。锁升级失败!退回到未决锁状态,阻隔其他进程获取共享锁。而A执行完select语句后,想要写数据库,因为B为释放保留锁,而数据库中只能有一个保留锁,因此A获取保留锁失败。

事物种类

SQLite 有三种不同的事务,使用不同的锁状态。事务可以开始于:DEFERRED、MMEDIATE或 EXCLUSIVE。事务类型在 BEGIN 命令中指定:
BEGIN [ DEFERRED | IMMEDIATE | EXCLUSIVE ] TRANSACTION;
一个 DEFERRED 事务不获取任何锁(直到它需要锁的时候),BEGIN 语句本身也不会做什么事情——它开始于 UNLOCK 状态。默认情况下就是这样的,如果仅仅用 BEGIN 开始一个事务,那么事务就是 DEFERRED 的,同时它不会获取任何锁;当对数据库进行第一次读操
作时,它会获取 SHARED 锁;同样,当进行第一次写操作时,它会获取 RESERVED 锁。由 BEGIN 开始的 IMMEDIATE 事务会尝试获取 RESERVED 锁。如果成功,BEGINIMMEDIATE 保证没有别的连接可以写数据库。但是,别的连接可以对数据库进行读操作;
但是,RESERVED 锁会阻止其它连接的 BEGIN IMMEDIATE 或者 BEGIN EXCLUSIVE 命
令,当其它连接执行上述命令时,会返回 SQLITE_BUSY 错误。这时你就可以对数据库进行修改操作了,但是你还不能提交,当你 COMMIT 时,会返回 SQLITE_BUSY 错误,这意味着还有其它的读事务没有完成,得等它们执行完后才能提交事务。EXCLUSIVE 事务会试着获取对数据库的 EXCLUSIVE 锁。这与 IMMEDIATE 类似,但是一
旦成功,EXCLUSIVE 事务保证没有其它的连接,所以就可对数据库进行读写操作了。上节那个例子的问题在于两个连接最终都想写数据库,但是它们都没有放弃各自原来的锁,最终,SHARED 锁导致了问题的出现。如果两个连接都以 BEGIN IMMEDIATE 开始事务,
那么死锁就不会发生。在这种情况下,在同一时刻只能有一个连接进入BEGIN IMMEDIATE,其它的连接就得等待。BEGIN IMMEDIATE 和 BEGIN EXCLUSIVE 通常被写事务使用。就像同步机制一样,它防止了死锁的产生。

基本的准则是:如果你正在使用的数据库没有其它的连接,用 BEGIN 就足够了。但是,如
果你使用的数据库有其它的连接也会对数据库进行写操作,就得使用 BEGIN IMMEDIATE
或 BEGIN EXCLUSIVE 开始你的事务

你可能感兴趣的:(SQLite3,sqlite,数据库,database)