🦏 第6章 内置函数
📄 一句话概括

系统函数查的是环境,不是数据:引擎版本、当前用户、当前库、连接号、上一次插入的自增值。 Demo 里 sqlite_version() 给 3.53.1——它是本沙箱这个引擎的版本,不是 MySQL 的; MySQL 那边写 SELECT VERSION()。 last_insert_id() 给 4(刚插进去那行的自增 id); DATABASE()、USER()、CONNECTION_ID() 在这里没有等价物,跑不了。

📝 本章和前面几章的关系

上一类算摘要,这一篇是「查自己」。 它几乎不参与业务逻辑,但排查问题的第一步全靠它。 「明明改了却没生效」这种问题,一半是连到了另一台库。

一、版本号:先确认自己连的是谁

SELECT VERSION() 是 MySQL 的写法,给 8.0.x。 本沙箱没有这个函数,对应的是 SELECT sqlite_version(),给 3.53.1。 教材原码是 VERSION(),这一句在本沙箱不执行。 正文里凡出现「教材原码、不在此执行」,都是这个意思。 为什么第一步就敲它:不同版本的语法、函数、默认行为都不一样。 同一个报错在 5.7 和 8.0 上原因可能完全不同,先确认版本能省一半时间。

二、last_insert_id:上一次插入的自增 id

插一行,再问 last_insert_id(),给的就是那行的 id。 Demo 里插入「四班」,拿到 4。 它是「当前连接上一次 INSERT」的值——换一个连接就变了,所以别指望在另一个会话里读到它。 批量插入时它给的是「第一行」的 id,不是最后一个。 这个细节在批量写子表时很关键:拿到第一行的 id,再按行数往后推,才能对上每一行。

三、数据库名、当前用户:教材原码,这里没有

MySQL 里常用这四个:DATABASE() 给当前库名、USER() 给登录用户、CONNECTION_ID() 给连接号、 @@version 也是版本号。 本沙箱是「一个文件一个库」,没有「当前库」这个概念,也没有登录用户, 所以这几个函数没法给等价物。 处理方式:教材里这几句照原样记,但标注「本沙箱不执行」,不硬编一个假结果。 测不出来的东西就写「测不出来」,别编。

四、系统函数能干什么

最实用的是自检:连上先 SELECT VERSION() 确认引擎、SELECT DATABASE() 确认没连错库。 两步五秒钟,能省几小时。 其次是审计:把 USER()、CONNECTION_ID() 写进操作日志, 出问题时能追到是哪个连接干的。 多个人共用一个账号时,这条尤其重要。 但别把它们写进业务逻辑——换个环境值就变了, 测试和线上的行为就不一致了。 判断「是不是生产环境」这种事,该在配置里做,不该在 SQL 里做。

五、动手 Demo:查版本、插一行、再问它的 id

Demo 三段:问版本号、插一行班级、看自增 id 落在哪儿。 这一段没有业务含义,全是「确认环境」的动作。

import _q46 as Q
c = Q.con()

# 版本号:这里问的是 SQLite
c.execute("SELECT sqlite_version() AS v")
# 刚插进去那行的自增 id
Q.run(c, "INSERT INTO classes (name, teacher, room) "
          "VALUES ('四班', '唐老师', 'B301')")
c.execute("SELECT LAST_INSERT_ID() AS new_id")
c.execute("SELECT id, name, teacher FROM classes ORDER BY id")

# MySQL 的 DATABASE()/USER()/CONNECTION_ID() 在这里没有等价物

实跑输出:

--------------------------------------------------------------------
第二十九课:系统函数——查环境,不是查数据
--------------------------------------------------------------------

  版本号:这里问的是 SQLite 的版本
  SQL> SELECT sqlite_version() AS v
  v
  -------
  3.53.1
  (1 行)

  **`3.53.1` 是本沙箱这个引擎的版本号。**
  **MySQL 那边对应的是 `SELECT VERSION()`,会给 `8.0.x`。**
  **教材原码是 `VERSION()`,本沙箱没有这个函数,所以不在这儿执行。**
  **这一句的用途是「连上之后先确认自己连的是哪个引擎、哪个版本」**,排查问题时第一步就该敲它。

  last_insert_id:刚插进去那行的自增 id
  SQL> INSERT INTO classes (name, teacher, room) VALUES ('四班', '唐老师', 'B301')
  影响 1 行

  SQL> SELECT LAST_INSERT_ID() AS new_id
  new_id
  -------
  4
  (1 行)

  SQL> SELECT id, name, teacher FROM classes ORDER BY id
  id     name     teacher
  ------------------------
  1      一班     马老师
  2      二班     孔老师
  3      三班     曹老师
  4      四班     唐老师
  (4 行)

  **插进去的那行拿到了 id = 4,`last_insert_id()` 给的就是 4。**
  **它是「当前连接上一次 INSERT」的值**,换一个连接就变了。
  **批量插入时它给的是「第一行」的 id**,不是最后一个。
  **这个细节在批量写子表时很关键**——别拿它去算最后一行的 id。

  数据库名、当前用户:教材原码,这里没有
  **MySQL 里常用这几个**:`DATABASE()` 给当前库名、`USER()` 给当前登录用户、
  `CONNECTION_ID()` 给连接号、`@@version` 也是版本号。
  **本沙箱是「一个文件一个库」,没有「当前库」这个概念,也没有登录用户**,
  所以这几个函数没法给等价物。 **教材里这几句照原样记,但在这里跑不了。**

  系统函数能干什么
  **最实用的是「自检」**:连上先 `SELECT VERSION()` 确认引擎、`SELECT DATABASE()` 确认没连错库。
  **线上出「明明改了却没生效」的问题,一半是连到了另一台库。**
  **其次是审计**:把 `USER()`、`CONNECTION_ID()` 写进日志,**出问题时能追到是哪个连接干的。**
  **但别把它们写进业务逻辑**——换个环境值就变了,测试和线上行为不一致。

第一段版本。 3.53.1——这是 SQLite 的版本,不是 MySQL 的。 第二段插入。 影响 1 行,四班进去了。 第三段自增 id。 给 4,因为原来三条记录占了 1、2、3。 再列一遍全表,能看到四班确实在第四行。

30 秒实验

  1. 再插一行,LAST_INSERT_ID() 会变几?
  2. 在另一个连接里问 LAST_INSERT_ID(),能读到这个 4 吗?
  3. SELECT VERSION() 在本沙箱会报什么错?试一次。

常见误区

  1. 把 DATABASE()、USER() 写进业务判断。 换环境值就变,测试与线上行为不一致。
  2. 批量插入后拿 last_insert_id() 当最后一个 id。 它给的是第一行的 id。
  3. 不确认版本就照抄网上的写法。 5.7 和 8.0 的默认行为差很多, 同一个问题可能根本不是同一个原因。
自测三问(点开对答案) 1. MySQL 里查版本、查当前库、查当前用户分别用什么函数? —— `VERSION()`、`DATABASE()`、`USER()`,另有 `CONNECTION_ID()` 给连接号。 2. `LAST_INSERT_ID()` 在批量插入时给哪一个 id? —— 给第一行的自增 id,不是最后一个。 3. 为什么系统函数不该写进业务逻辑? —— 值随环境变化,测试与线上行为就不一致了;这类判断该放配置里。

系统函数是最短的一篇。下一篇收尾——cast、if、ifnull 三个最常用的杂项。

相关笔记