🛢️ 第6章 数据表操作
📄 一句话概括

DESCRIBE、DESC、SHOW COLUMNS FROM 是同一条语句,给六列。 它看不到引擎和字符集,那两样只在 SHOW CREATE TABLE 里有。 接手一个库的第一个动作:这三句连着敲,别直接 SELECT *。

📝 本章和前面几章的关系

6.1 建完表,这一篇讲怎么验收——建出来的东西是不是你想的那样。 它是第 4 章「查看数据库」的下一层:那篇看库,这篇看表。 这一篇的三句查法,在第 6.3 节改结构前后都要各跑一次。

一、DESCRIBE:看列的六件事

三个名字一条语句,随便用哪个都一样。

DESCRIBE tb_member;
DESC tb_member;
SHOW COLUMNS FROM tb_member;

它给六列,教材 6.2 逐列讲过,这里按「最常看」排序:

列 看什么 常用来判断
Null 能不能为空 NO 表示必须给值
Default 默认值 插数据时可以省略这一列
Key 是不是索引 PRI 主键、UNI 唯一、MUL 普通
Extra 额外属性 auto_increment 在这一列
Type 类型 是不是选窄了
Field 列名 拼 SQL 时别写错

第二列有一个坑:Default 显示 NULL 有两种含义。 一种是「默认值就是 NULL」,另一种是「没定默认值」——在这里长得一模一样。 要分清得去看 SHOW CREATE TABLE。 第三列也有盲区:它只说「有索引」,不说索引叫什么、几列、是不是唯一。 这些要看 SHOW INDEX。

二、SHOW CREATE TABLE:唯一能看到引擎的地方

这句话给的是「重新建一张一模一样的表」所需的完整语句。 所以它必然包含 DESCRIBE 看不到的东西:引擎、字符集、排序规则、外键约束名。

SHOW CREATE TABLE tb_member\G

末尾那个 \G 是把结果竖着打。 不加它,一条长语句会被终端折成难读的几行;加上它,一列一行,好读得多。 这句话的三个用途,从轻到重: 一是想知道引擎和字符集;二是想照抄一张表的结构;三是备份建表语句。 第三个用途最实在。 数据可以用 mysqldump 备,但「这张表本来长什么样」只有这一句能给全。 接手老系统时先把所有表的 SHOW CREATE TABLE 存一份,比存数据有用。

三、索引和外键要另查

表结构是三样东西:列、索引、约束。三样在不同的地方。

要看什么 用哪一句
列定义 DESCRIBE 表
完整建表语句(含引擎、字符集) SHOW CREATE TABLE 表
索引明细 SHOW INDEX FROM 表
外键 查 information_schema.KEY_COLUMN_USAGE
表的行数、大小、引擎 SHOW TABLE STATUS LIKE '表'

这五句凑齐,才算把一张表看全了。 很多人只跑第一句,然后在「为什么这条查询不走索引」上卡半天——因为索引根本没看。 第五行是运营视角:Rows 那一列是估算值,不是精确行数。 InnoDB 的 Rows 是从统计信息里读的,误差可能到几成,别拿它当准确数。

四、接手一个库,先跑这三句

教材 6.2 讲完语法就结束了,实际工作里这三句是连着用的。

SHOW TABLES;                        -- 有哪些表
SHOW CREATE TABLE 表名;              -- 这张表长什么样
SHOW INDEX FROM 表名;                -- 索引有哪些

第一句给名单,第二句给结构,第三句给性能线索。 三句都跑完,你对这个库的了解就超过「能查出数据」的层次了。 为什么强调「别直接 SELECT *」? 因为它只给你数据,不给你结构。 而线上事故八成出在结构上:类型选窄了、索引少了、字符集不对。 还有一个更实用的理由:SELECT * 在大表上会把整行拉出来,包括那些 TEXT 大字段。 查结构不产生任何数据读取,是安全的操作。

五、动手 Demo:三种看法,信息量差很多

这一课把三种查法摆在一起:DESCRIBE、SHOW CREATE TABLE、SHOW INDEX。 沙箱里对应的是 PRAGMA table_info、sqlite_master、PRAGMA index_list。 看完你会知道每种查法各漏了什么。

# 一、列定义:六列,和 DESCRIBE 一一对应
for cid, name, typ, notnull, dflt, pk in c.execute("PRAGMA table_info(tb_member)"):
    print(name, typ, notnull, dflt, pk)

# 二、完整建表语句:只有这里能看到 CHECK 和 UNIQUE 的原文
print(c.execute("SELECT sql FROM sqlite_master WHERE name = 'tb_member'").fetchone()[0])

# 三、索引:DESCRIBE 的 Key 列看不出这些
for seq, name, uniq, origin, partial in c.execute("PRAGMA index_list(tb_member)"):
    print(name, uniq, origin)

实跑输出:

--------------------------------------------------------------------
第二十六课:三种看法,信息量差很多
--------------------------------------------------------------------

  **教材 6.2 讲 `DESCRIBE`。它好用,但它看不到引擎和字符集。**
  **要全的信息得用 `SHOW CREATE TABLE`。**
  **这一课把三种查法摆在一起,看它们各自能看到什么、看不到什么。**

  一、DESCRIBE:看列的六件事
  --------------------------------------------------------------------
  **`DESCRIBE`、`DESC`、`SHOW COLUMNS FROM` 三个是同一条语句的三种写法。**
  **它给六列:字段名、类型、能不能为空、是不是键、默认值、额外信息。**

  字段     类型     空   键   默认               额外
  -------------------------------------------------------------
  id       INTEGER  YES  PRI  NULL               auto_increment
  account  TEXT     NO        NULL
  nick     TEXT     NO        ''
  age      INTEGER  YES       NULL
  score    REAL     YES       0
  joined   TEXT     YES       CURRENT_TIMESTAMP
  **六列里最常看的是第三列和第五列。**
  **第三列是「能不能为空」,`NO` 表示插数据时必须给值(或者有默认值)。**
  **第一行那格是 `YES`,这一点跟 MySQL 不一样,得说明一下。**
  **SQLite 的 `INTEGER PRIMARY KEY` 并不拦 NULL,插 NULL 会自动填自增值;MySQL 的主键列必然是 `NO`。**
  **所以这一格别当结论,当「两边实现不同」的例子看。**
  **第五列那个 `NULL` 有两种含义,要分清。**
  **一种是「默认值就是 NULL」,另一种是「没定默认值」——`DESCRIBE` 里这两者长得一样。**
  **要分清得去看 `SHOW CREATE TABLE`。**

  二、SHOW CREATE TABLE:唯一能看到引擎和字符集的地方
  --------------------------------------------------------------------
  **沙箱里这张表定义存在 `sqlite_master` 里,MySQL 里用 `SHOW CREATE TABLE` 取。**
  **这是它给出来的原文(我把长行折了一下,逗号留在原处):**
  `CREATE TABLE tb_member (id INTEGER PRIMARY KEY AUTOINCREMENT,`
  `account TEXT NOT NULL UNIQUE,`
  `nick TEXT NOT NULL DEFAULT '',`
  `age INTEGER CHECK (age BETWEEN 1 AND 150),`
  `score REAL DEFAULT 0,`
  `joined TEXT DEFAULT CURRENT_TIMESTAMP)`
  **这里能看到的、`DESCRIBE` 看不到的东西有三样。**
  **一是自增属性挂在哪一列上;二是唯一约束;三是 CHECK 约束的完整条件。**
  **真 MySQL 的输出还会多一样最关键的:末尾那行 `ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`。**
  **所以「这张表到底用的什么引擎」只有一个可靠答案——`SHOW CREATE TABLE`。**
  **`DESCRIBE` 看不出来,`SELECT *` 更看不出来。**

  三、索引和外键要另查
  --------------------------------------------------------------------

  索引名                        列       唯一  来路
  -------------------------------------------------------
  i_nick                        nick     否    自建
  sqlite_autoindex_tb_member_1  account  是    约束自动建
  **`DESCRIBE` 的输出里,「键」那一列只有 `PRI`、`UNI`、`MUL` 三个值。**
  **它告诉你「有索引」,但不告诉你索引叫什么、几列、是不是唯一。**
  **所以查表结构的正确姿势是三句连着敲:**

  [教材原码 · 本沙箱不执行]
  | DESCRIBE tb_member;          -- 列定义
  | SHOW CREATE TABLE tb_member; -- 完整建表语句(引擎、字符集都在这)
  | SHOW INDEX FROM tb_member;   -- 索引明细
  ② 教材原码,本沙箱不执行

  **三句各有各的盲区,凑齐才是一张表的样子。**
  **接手的第一个动作就应该是这三句,而不是直接 `SELECT *`。**

第一段那张六列表就是 DESCRIBE 的样子。 注意 id 那一行的「空」列给的是 YES,这一点跟 MySQL 不一样。 SQLite 的 INTEGER PRIMARY KEY 并不拦 NULL,插 NULL 会自动填自增值; MySQL 的主键列必然是 NO。 第二段把建表语句原文打出来了。 DESCRIBE 看不到的三样东西都在这里:自增属性挂在哪列、唯一约束、CHECK 的完整条件。 真 MySQL 还会多一样最关键的:末尾那行 ENGINE=InnoDB DEFAULT CHARSET=utf8mb4。 第三段那张索引表有个细节:sqlite_autoindex_tb_member_1。 它是 UNIQUE 约束自动建的索引,名字是系统起的。 所以「这条查询为什么走了个我没建过的索引」,答案往往是某个约束替你建的。 三张表看完可以下一个结论:DESCRIBE 适合快速看列,要全的信息只能靠后两种。

30 秒实验

  1. 给表加一列 remark TEXT,再跑一次 DESCRIBE,看新列在最后一行。 新列总在最后,因为 ADD COLUMN 只能往末尾加。 想插在中间只能重建表。
  2. 把 nick 列的默认值从 '' 改成 '匿名',看 DESCRIBE 的 Default 列怎么变。 跟着变。这一列就是「不填时补什么」的直接体现。
  3. 跑一次 SHOW TABLE STATUS LIKE 'tb_member',看 Rows 和实际行数差多少。 大概率不准。InnoDB 的 Rows 是统计信息估算值,不是 COUNT(*)。

常见误区

  1. 「DESCRIBE 能看到表的一切。」 看不到引擎、字符集、排序规则、外键、索引明细。 这五样恰恰是排查问题时最常要看的。
  2. 「SHOW TABLE STATUS 的 Rows 就是行数。」 是估算值。 要准确数只能用 COUNT(*),代价是全表或全索引扫一遍。
  3. 「建表语句太长,用 SELECT * 看一列名字就行。」 SELECT * 在空表上什么都看不到,还会拉全部大字段。 看结构就用看结构的语句。
  4. 「SHOW CREATE TABLE 的输出可以原样再执行一遍。」 表已存在时会报错,要先把 CREATE TABLE 改成 CREATE TABLE IF NOT EXISTS,或者换个表名。 而且它带 AUTO_INCREMENT=当前值,抄过去会连计数器一起抄。
自测三问(点开对答案) 1. 想知道一张表用的什么引擎,用哪一句? —— **`SHOW CREATE TABLE 表名`,末尾那行写着 `ENGINE=...`。** **`DESCRIBE` 看不到,`SHOW TABLE STATUS` 也能看到但信息更杂。** 2. `DESCRIBE` 的 `Key` 列有哪三个值? —— **`PRI` 主键、`UNI` 唯一索引的第一列、`MUL` 普通索引的第一列。** **它只说「有」,不说索引名和列数。** 3. 为什么接手一个库要先跑 `SHOW CREATE TABLE`? —— **因为它是唯一能给全结构的一句,包含引擎、字符集、约束名。** **而且它可以直接当建表脚本存档,比存数据更能还原现场。**

6.2 到这儿结束。下一篇 6.3 讲改表结构——建表时想漏的东西,后面都得靠 ALTER TABLE 补。

相关笔记