🛢️ 第10章 常用函数
📄 一句话概括

排错第一步是把版本号打出来,因为「哪些语法能用」由它决定。 沙箱没有「连接」也没有「用户」,这两个概念在嵌入式库里不存在。 字符集和排序方式决定 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 秒实验

  1. 把 PRAGMA encoding 换成 PRAGMA encoding = 'UTF-16',看它报不报错。
  2. 建一个索引再查 sqlite_master,看多出什么 type。
  3. 用 PRAGMA database_list 看 ATTACH 前后的行数变化。

常见误区

  1. 查「为什么这句跑不了」时先怀疑权限,其实该先看版本号。
  2. 以为 NOCASE 是「什么都不区分」,其实它只管大小写。
  3. 把 SHOW 命令当唯一手段,不知道 information_schema 能用 SQL 查。
自测三问(点开对答案) 1. 为什么排错要看版本号? —— 因为「哪些语法能用」由版本决定,比如 `INTERSECT` 要 8.0.31、函数索引要 8.0.13。 版本不对,后面的权限、字符集排查都是白费。 2. SQLite 为什么没有 `USER()`? —— 它是嵌入式库,一个进程直接打开文件,没有登录也没有连接池, 「谁在操作」这件事由操作系统的文件权限管。 3. `BINARY`、`NOCASE`、`RTRIM` 三种排序方式各管什么? —— `BINARY` 按字节比,`NOCASE` 不区分大小写,`RTRIM` 忽略末尾空格。

下一篇 10.7 收尾:格式化、类型转换、字符集转换,把「类型」这条线收一下。

相关笔记