排错第一步是把版本号打出来,因为「哪些语法能用」由它决定。
沙箱没有「连接」也没有「用户」,这两个概念在嵌入式库里不存在。
字符集和排序方式决定 LIKE 的大小写行为,不由语句决定。
10.6 讲三类:版本号与连接数、当前用户、字符集与排序方式。 沙箱是个嵌入式库,这一类里有一半只能讲「这里没有」。 缺的部分不硬造,改成「MySQL 给什么、这里能用什么代替、代替不了就说明」。
一、版本号:决定哪些语法能用
VERSION() 是 MySQL 的写法,沙箱对应 sqlite_version(),给的是 3.53.1。
版本号看着无关紧要,其实决定了你能用哪些语法。
| 特性 | 最低版本 |
|---|---|
INTERSECT / EXCEPT |
MySQL 8.0.31 |
| 函数索引 | MySQL 8.0.13 |
WITH ROLLUP |
很早就有 |
PRAGMA function_list |
SQLite 3.37 |
所以排查「为什么这句在别人机器上能跑」,第一件事就是比版本号。
比完再谈权限、字符集、sql_mode,顺序反了会白忙一场。
版本号还有一个用处:确认自己看的文档对不对。
5.7 和 8.0 的差别很大,照 5.7 的书写 8.0 的句子,会撞一堆莫名其妙的问题。
二、当前库名与库列表
MySQL 的 DATABASE() 返回当前选中的库名,USE 库名 之后就有值。
SQLite 没有「选中库」这个概念,一个连接直接对着文件。
所以它能列库不能选库:PRAGMA database_list 会告诉你这个连接上有几个库。
ATTACH 之后那个列表会多一行,这是「挂载」不是「切换」。
多库操作时靠 库名.表名 限定,不能靠「切过去」。
这一点第 6 章讲 ATTACH 时提过,是同一件事的另一面。
三、当前用户与连接:这里没有这个概念
USER()、CURRENT_USER()、CONNECTION_ID() 在沙箱里一个都没有。
这不是「不支持」,是「嵌入式库根本没有这些概念」。
| 写法 | MySQL 给什么 | 沙箱 |
|---|---|---|
user() |
当前登录的用户@主机 | 没有这个概念 |
current_user() |
鉴权通过的账号 | 没有这个概念 |
connection_id() |
当前连接的编号 | 没有这个概念 |
一个进程直接打开一个文件,谈不上登录,也谈不上连接池。 所以做权限和审计的那一套,在 SQLite 里是操作系统文件权限的事。 这也是它适合单机、不适合多用户服务端的根本原因。 不只是并发性能的问题,是「谁在操作」这件事它压根不记录。
四、字符集与排序方式
沙箱的字符集固定是 UTF-8,改不了;MySQL 可以在库、表、列三层各指定一个。 「三层各指定一个」听着灵活,实际是乱码的主要来源之一。
| 项 | 沙箱 |
|---|---|
PRAGMA encoding |
UTF-8 |
PRAGMA collation_list |
3 种 |
charset() |
沙箱无此函数 |
那 3 种排序方式是 BINARY、NOCASE、RTRIM。
BINARY 按字节比,NOCASE 不区分大小写,RTRIM 忽略末尾空格。
三个各管一件事,不是「一个严格、两个宽松」的关系。
搞混了会以为「NOCASE 就是宽松版」,其实它只管大小写。
MySQL 的排序方式名字长得像 utf8mb4_0900_ai_ci,中间那个 ci 表示不区分大小写。
LIKE 的大小写行为、ORDER BY 的顺序,全由这个后缀决定。
五、元数据也是数据
sqlite_master 相当于 MySQL 的 information_schema,它记着库里所有对象。
想看表结构、索引、视图的定义,都从这里查,比记一堆 SHOW 命令可靠。
MySQL 那边对应的是 SHOW TABLES、SHOW CREATE TABLE。
再深一层还有 information_schema,那几张表把元数据也当成了普通的表。
两边思路一样:元数据也是一种数据,能用 SQL 查就不用背命令。
能查就能筛、能连、能导出,这一点是 SHOW 命令比不了的。
代价是那些表的结构各版本会变,写死在代码里迟早要改。
所以拿它做运维脚本可以,做业务逻辑要谨慎。
六、动手 Demo:系统信息函数,和「这里没有这个概念」
这一段的前四节都是「查环境」:版本、库、用户、字符集。 后三样沙箱只能给一半,缺的那一半正好说明 SQLite 是什么定位。
import sqlite3
c = sqlite3.connect(u":memory:")
# ① 版本号
print(c.execute(u"SELECT sqlite_version()").fetchone())
# ② 库列表
print(c.execute(u"PRAGMA database_list").fetchall())
# ③ 字符集与排序方式
print(c.execute(u"PRAGMA encoding").fetchone())
print([x[1] for x in c.execute(u"PRAGMA collation_list").fetchall()])
# ④ 元数据
c.execute(u"CREATE TABLE t (id INTEGER PRIMARY KEY)")
print(c.execute(u"SELECT type, COUNT(*) FROM sqlite_master "
u"GROUP BY type").fetchall())
实跑输出:
--------------------------------------------------------------------
第四十九课:系统信息函数,和「这里没有这个概念」
--------------------------------------------------------------------
**教材 10.6 讲三类:版本号与连接数、当前用户、字符集与排序方式。**
**沙箱是个嵌入式库,没有「连接」也没有「用户」,所以这一课有一半是「讲清缺什么」。**
**缺的部分不硬造,改成「MySQL 给什么、这里能用什么代替、代替不了就说明」。**
一、版本号
--------------------------------------------------------------------
写法 结果
----------------------------------
`sqlite_version()` 3.53.1
`version()` 沙箱无此函数
`sqlite_source_id()` 沙箱无此函数
**`VERSION()` 是 MySQL 查版本号的函数,沙箱对应的是 `sqlite_version()`。**
**版本号这件事看着无关紧要,其实决定了「哪些语法能用」。**
**比如 `INTERSECT` 要 8.0.31 以上、`WITH ROLLUP` 一直都在、函数索引要 8.0.13 以上。**
**所以排查「为什么这句在别人机器上能跑」,第一件事就是比版本号。**
二、当前库名与库列表
--------------------------------------------------------------------
项 值
-------------------------------------------
`PRAGMA database_list` 的库数 1
主库名 main
`database()` 沙箱无此函数
**MySQL 的 `DATABASE()` 返回当前选中的库名,`USE 库名` 之后就有值。**
**SQLite 没有「选中库」这个概念,一个连接直接对着文件,所以只能列库不能选库。**
**`ATTACH` 之后 `PRAGMA database_list` 会多一行,这是「挂载」不是「切换」。**
**多库操作时靠 `库名.表名` 限定,这一点和第 6 章讲 `ATTACH` 时是一致的。**
三、当前用户与连接
--------------------------------------------------------------------
写法 MySQL 给什么 沙箱
----------------------------------------------------
`user()` 当前登录的用户@主机 没有这个概念
`current_user()` 鉴权通过的那个账号 没有这个概念
`connection_id()` 当前连接的编号 没有这个概念
**这三行不是「沙箱不支持」,是「嵌入式库根本没有这些概念」。**
**一个进程直接打开一个文件,谈不上登录,也谈不上连接池。**
**所以做权限和审计的那一套,在 SQLite 里是操作系统文件权限的事。**
**这也是它适合单机、不适合多用户服务端的根本原因,不只是性能问题。**
四、字符集与排序方式
--------------------------------------------------------------------
项 值
----------------------------------------------
`PRAGMA encoding` UTF-8
`PRAGMA collation_list` 里有几种 3
`charset('abc')` 沙箱无此函数
`collation('abc')` 沙箱无此函数
**沙箱的字符集固定是 UTF-8,改不了。MySQL 可以在库、表、列三层各指定一个。**
**「三层各指定一个」听着灵活,实际是乱码的主要来源之一。**
**排序方式这里一共 3 种:`RTRIM`、`NOCASE`、`BINARY`。**
**`BINARY` 按字节比,`NOCASE` 不区分大小写,`RTRIM` 忽略末尾空格。**
**三个各管一件事,不是「一个严格、两个宽松」的关系。**
**MySQL 的排序方式名字长得像 `utf8mb4_0900_ai_ci`,中间那个 `ci` 就是「不区分大小写」。**
**`LIKE` 的大小写行为、`ORDER BY` 的顺序,全都由这个后缀决定,不由语句决定。**
五、把能查的查全:sqlite_master
--------------------------------------------------------------------
对象类型 个数
--------------
table 1
**`sqlite_master` 相当于 MySQL 的 `information_schema`,它记着库里所有对象。**
**想看表结构、索引、视图的定义,都从这里查,比记一堆 `SHOW` 命令可靠。**
**MySQL 那边对应的是 `SHOW TABLES`、`SHOW CREATE TABLE`。**
**再深一层还有 `information_schema`,那几张表把元数据也当成了普通的表。**
**两边思路一样:元数据也是一种数据,能用 SQL 查就不用背命令。**
版本号那一行是 3.53.1,比 3.37 新,所以 PRAGMA function_list 能用。
这就是「版本决定能力」的一个具体例子。
库列表只有一行 (0, 'main', ''):连接上没有别名的内存库。
换成文件库,第三个字段就是文件路径。
字符集是 UTF-8、排序方式 3 种,这两个数在多用户场景下会复杂得多。
MySQL 里同一个库不同表可以有不同字符集,那才是乱码的高发区。
元数据那一行给 table 1:一张表。
索引、视图、触发器都会在这里出现,type 字段区分。
30 秒实验
- 把
PRAGMA encoding换成PRAGMA encoding = 'UTF-16',看它报不报错。 - 建一个索引再查
sqlite_master,看多出什么type。 - 用
PRAGMA database_list看ATTACH前后的行数变化。
常见误区
- 查「为什么这句跑不了」时先怀疑权限,其实该先看版本号。
- 以为
NOCASE是「什么都不区分」,其实它只管大小写。 - 把
SHOW命令当唯一手段,不知道information_schema能用 SQL 查。
自测三问(点开对答案)
1. 为什么排错要看版本号? —— 因为「哪些语法能用」由版本决定,比如 `INTERSECT` 要 8.0.31、函数索引要 8.0.13。 版本不对,后面的权限、字符集排查都是白费。 2. SQLite 为什么没有 `USER()`? —— 它是嵌入式库,一个进程直接打开文件,没有登录也没有连接池, 「谁在操作」这件事由操作系统的文件权限管。 3. `BINARY`、`NOCASE`、`RTRIM` 三种排序方式各管什么? —— `BINARY` 按字节比,`NOCASE` 不区分大小写,`RTRIM` 忽略末尾空格。下一篇 10.7 收尾:格式化、类型转换、字符集转换,把「类型」这条线收一下。
- ☐ (本篇完,下一篇 10.7 其他函数)