🛢️ 第4章 数据库操作
📄 一句话概括

USE 库名; 改的是「这条连接」的状态,不是「这个库」的状态。 所以换一条连接,当前库就没了——图形工具每次刷新都得重发一次 USE。 忘了 USE 就建表,报的是 1046:No database selected。

📝 本章和前面几章的关系

上一篇看库,这一篇选库。一句话的语法,但背后是「会话状态」这个概念。 它是理解连接池、事务、会话变量的起点。 这一篇讲清了「状态属于连接」,后面第 15 章讲会话变量时就不用重复解释。 和第 24 章的关系:JDBC 的 Connection.setCatalog() 就是这条 USE。

一、USE 到底改了什么

USE db_shop; 的作用只有一件事:把「这条连接的当前库」设成 db_shop。 之后不带库名的表名,就都按这个库来解释。

操作 改的是谁的状态 影响别人吗
USE 库名 这条连接 不影响
SET @x = 1 这条连接(会话变量) 不影响
SET GLOBAL x = 1 整个服务端 影响所有人
CREATE DATABASE 磁盘上的数据 影响所有人

前三行的区别就是这一篇的核心:有的状态是「我的」,有的是「大家的」。 搞混了,就会出现「我这边好好的,他那边报错」这种查不出原因的问题。

二、忘了 USE 会怎样

ERROR 1046 (3D000): No database selected 这个错误码值得记:它排在「新手最常见报错」的前三名。

语句 没选库时
CREATE TABLE t (...) 1046,不知道该建到哪儿
SELECT * FROM t 1146,表不存在(它在找「当前库」里的 t)
SELECT 1 能跑,它不需要任何库
SHOW DATABASES 能跑,查的是全局字典

第三、四行说明一件事:不是所有语句都需要「当前库」。 只有「提到表名」的语句才需要——因为表名要在某个库里解析。

三、跨库查询:加前缀就行

不想切库,也可以直接写 库名.表名。 这在 join 两个库的表时特别有用。 MySQL 上直接写就行,不用额外动作。 SQLite 得先 ATTACH 把另一个库挂上来,然后一样用前缀。 两种做法的效果一样,机制不同。 跨库查询有个隐含代价:它绕过了「一个库一个连接」的隔离。 如果两个库在不同实例上,MySQL 是做不到的——那得用别的办法(比如中间件)。

四、连接池里那个「脏状态」

连接池复用连接,但「当前库」这个状态是跟着连接走的。 上一个用户 USE db_a,下一个用户拿到同一条连接,当前库还是 db_a。 所以成熟的连接池会在归还连接前重置状态(发一条 USE 或者 RESET)。 没重置的池子,会出现「偶发查错库」这种极难复现的问题。 这也是为什么很多团队规定:SQL 里一律写全 库名.表名,不依赖当前库。 啰嗦一点,但不会错。

五、动手 Demo:USE 干了什么,以及为什么忘了它会报 1046

这一课把「当前库」这个状态演出来:没选、选了、换一条连接。 模型里的 cur 就是连接上的那个状态,和 MySQL 的实现是一个意思。

cat = Catalog(DATA)
for n in ("db_shop", "db_blog"):
    cat.create(n)

# 没选库就想建表
try:
    cat.conn()
except ValueError as e:
    print(e)          # ERROR 1046 (3D000): No database selected

# 选了之后
cat.use("db_shop")
c = cat.conn()
c.execute("CREATE TABLE t (id INTEGER PRIMARY KEY)")

# 换一条连接:当前库又没了
cat2 = Catalog.__new__(Catalog)
cat2.root = cat.root
cat2.cur = None
try:
    cat2.conn()
except ValueError as e:
    print(e)          # 又是 1046

# 跨库查询:加库名前缀
conn2.execute("ATTACH DATABASE ? AS db_blog", (path,))
print(conn2.execute("SELECT * FROM db_blog.t").fetchall())

实跑输出:

--------------------------------------------------------------------
第二十课:USE 干了什么,以及为什么忘了它会报 1046
--------------------------------------------------------------------

  **教材 4.4 节讲「选择数据库」,一句话:`USE 库名;`。**
  **但它背后的东西值得说清楚——「当前库」是连接上的一个状态。**
  **忘了 USE 就建表,报的是 `ERROR 1046 (3D000): No database selected`。**
  **这一课把这个状态演出来。**

  一、USE 改的是这条连接的状态
  --------------------------------------------------------------------
  **没选库时:**
  **ERROR 1046 (3D000): No database selected**
  **选了之后:**
  **`USE db_shop` 之后,建表就落在 db_shop 里。**
  **注意:这个状态属于「连接」,不属于「数据库」。**

  二、换一条连接,当前库就没了
  --------------------------------------------------------------------
  **新连接一上来:ERROR 1046 (3D000): No database selected**
  **这就是为什么图形工具每次刷新都要重发一次 `USE`。**
  **也是为什么连接池里的连接「当前库」可能是脏的**——上一个用户选的库还在。

  三、跨库查询:加库名前缀就行
  --------------------------------------------------------------------

  [教材原码 · 本沙箱不执行]
  | SELECT * FROM db_blog.t;
    (不用切库,直接加库名前缀)

  SELECT *
  FROM db_blog.t

  mysql> SELECT * FROM db_blog.t
  id v
  ---------------
  1  来自 db_blog
  1 row in set
  **MySQL 上直接写 `库名.表名` 就行,不用 ATTACH;**
  **SQLite 得先挂上来。两边的「跨库」意思不完全一样。**

第一段那个 1046 是最常见的报错之一。 注意它是 3D000 段,属于「连接与库选择」这一类,不是 SQL 语法错。 第二段 USE db_shop 之后建表就落进了 db_shop。 关键在「这条连接」四个字:状态属于连接,不属于库。 第三段换一条连接,当前库就没了。 这不是模型的怪癖,MySQL 完全一样——每条连接各有一个「当前库」。 所以图形工具每次刷新页面都要重发一次 USE。 第四段跨库查询:SQLite 要 ATTACH,MySQL 不用。 这个差别不大,但写迁移脚本时得知道。

30 秒实验

  1. 把 Demo 里第二段的 cat.use 删掉,看建表那行报什么。1046。 这说明「没选库」和「库不存在」是两个不同的错——后者是 1049。
  2. 在 Demo 里连续 USE db_shop 两次,看第二次返回什么。 还是 Database changed,它不判断「是不是同一个」。 所以重复 USE 是安全的,代价只有一次往返。
  3. 想一想:为什么不在每条 SQL 里都强制写库名?因为写全了很长,而且大多数时候当前库是固定的。 折中办法是「连接初始化时 USE 一次,之后依赖它」。

常见误区

  1. 「USE 是切库,切完别人也受影响。」 只影响你自己这条连接。 同一个库上别人怎么查,跟你 USE 了哪个库一点关系都没有。
  2. 「连接池里的连接,当前库总是对的。」 不一定。当前库跟着连接走,池子不给重置就会串。 所以很多团队规定 SQL 里一律写全 库名.表名。
  3. 「不选库就什么都不能干。」 SELECT 1 能跑,SHOW DATABASES 也能跑。 只有「提到表名」的语句才需要当前库。
自测三问(点开对答案) 1. `USE` 改的是谁的状态? —— **这条连接的当前库。** **不影响别人,也不改数据库本身。** 2. 没选库时执行 `SELECT * FROM t` 报什么错? —— **先报 1046(No database selected)为准;如果当前库存在但没有 t,才是 1146。** **两种情况要分清:一个是「不知道去哪个库找」,一个是「库找到了表没有」。** 3. 连接池为什么会「串库」? —— **因为当前库是连接上的状态,复用连接就复用了上一个用户的当前库。** **解决办法是池子归还时重置,或者 SQL 里写全库名。**

下一篇讲改库:一条 ALTER DATABASE 能改什么、改不动什么,以及为什么它「只改以后」。

相关笔记