🛢️ 第2章 初识MySQL
📄 一句话概括

教材给的三条方法——多练、看文档、多总结——都对,但太笼统。 这一篇把它们落到具体动作:造错、看计划、写小项目。 最实用的一条是「学会读报错」:错误码的号段本身就告诉你该查哪一层。

📝 本章和前面几章的关系

这是第 2 章最后一节,教材在这一节收尾,讲方法。 它是后面 22 章的用法说明书。 第 4 章到第 21 章全是语法和操作,学得快不快,取决于这一篇说的三个动作做没做。 和第 1 章的关系:第 1 章说「为什么要有数据库」,这一篇说「怎么把它学进手」。

一、把笼统的方法拆成动作

教材讲方法,讲了三条:多动手、多看官方文档、多总结。 这三条都对,问题是听完还是不知道明天该干什么。

阶段 该干什么 别干什么
第一周 把安装、建库建表、增删改查跑通 别急着看索引原理
第二周 把第 9 章的 join 写法全部手敲一遍 别复制粘贴
第三周 给自己造二十个错,逐个读报错 别一看到错就搜

「手敲」不是仪式感。 复制粘贴跳过的是「语法在你脑子里过一遍」这一步,而这一步才是学习发生的时刻。 第三周那条最反直觉,也最有用。 别人踩过的坑你未必踩得到,自己造出来的错一定记得住。

二、把报错当老师

报错信息里其实全给了:错误码、位置、对象名。 只要会看号段,八成的报错当场能定位,根本不用搜。

号段 谁报的 先查什么
2xxx 客户端,还没连上服务端 服务起没起、端口通不通、账号对不对
1xxx 服务端,SQL、数据、权限、锁 报错里点名的表名、列名、关键字
12xx 服务端,锁与事务 谁在持锁、事务是不是太长
ERRS = [
    "ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (10061)",
    "ERROR 1064 (42000): You have an error in your SQL syntax near 'SELEC'",
    "ERROR 1146 (42S02): Table 'db.tb' doesn't exist",
    "ERROR 1045 (28000): Access denied for user 'root'@'localhost'",
    "ERROR 1213 (40001): Deadlock found when trying to get lock",
]

def triage(msg):
    code = int(msg.split()[1])
    if code >= 2000 and code < 3000:
        return code, "连接层", "先查服务、端口、账号,别动 SQL"
    if code in (1213, 1205):
        return code, "锁与事务", "看谁持有锁,缩短事务,别加大超时"
    if code in (1045, 1044):
        return code, "权限", "查用户、主机、授权范围"
    return code, "SQL 与对象", "先看报错里点名的表、列、关键字"

def wid(s):
    return sum(2 if ord(ch) > 127 else 1 for ch in s)

def pad(s, w):
    return s + " " * (w - wid(s))

print(pad("错误码", 8) + pad("类别", 12) + "第一条动作")
print("-" * 60)
for m in ERRS:
    code, kind, act = triage(m)
    print(pad("%d" % code, 8) + pad(kind, 12) + act)

这个分类器只有二十来行,但它把「先查什么」这件事定死了。 看到 2003 还去改 SQL,就是在错的地方使劲。 读报错的四步,按顺序来: 先看号段,再看对象名,然后看位置(at line、near),最后才去搜。 搜的时候带上错误码,别只搜「MySQL 报错」四个字。

三、三条练习路线

语法是背不完的,能背下来的那部分恰恰最不值钱——查手册就有。 值钱的是「知道什么时候该用什么」。

路线 具体做法 练到什么
造错 关键字拼错、列名拼错、主键插重 一眼看出报错指向哪
看计划 每条 SQL 前面加 EXPLAIN 知道它走没走索引
写小项目 一个带登录、分页、统计的小站 把第 1 到 20 章串起来

三条里最值钱的是第三条。 一个真实的小项目会把「必须记住的那 20%」逼出来—— 分页要 LIMIT、统计要 GROUP BY、去重要 DISTINCT、慢要加索引。 第二条最容易被跳过,因为它没有即时反馈。 但「这条 SQL 走没走索引」是 MySQL 里最值钱的一个判断。 它决定了你的系统是十毫秒还是十秒。

四、动手 Demo:五个最常见的错误码,真造出来看

这一课把五个最常见的错真造出来,对照着看它的结构。 沙箱里没有 MySQL,报错文本是 SQLite 的;MySQL 的措辞不同,结构一样。 认识结构比背原文有用。

import sqlite3
c = sqlite3.connect(":memory:")
c.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
c.execute("INSERT INTO t VALUES (1, 'a')")

CASES = [
    ("1064 语法错", "SELEC * FROM t"),
    ("1054 未知列", "SELECT nmae FROM t"),
    ("1146 表不存在", "SELECT * FROM tt"),
    ("1062 主键重复", "INSERT INTO t VALUES (1, 'b')"),
    ("1048 列不能为空", "INSERT INTO t (id) VALUES (2)"),
]
for name, sql in CASES:
    try:
        c.execute(sql)
    except sqlite3.Error as e:
        print("%-16s %s" % (name, e))

实跑输出:

--------------------------------------------------------------------
第八课:报错怎么读——五个最常见的错误码
--------------------------------------------------------------------

  **教材最后一节叫「如何学好 MySQL」,讲的是方法。**
  **方法里最实用的一条是:先学会读报错。**
  **报错信息里其实全给了:错误码、位置、原因。**
  **这一课把最常见的五个错真造出来,对照着看。**

  一、五个错,逐个造
  --------------------------------------------------------------------
  **1064 语法错**
  **SQL:`SELEC * FROM t`**
  **沙箱报:`near "SELEC": syntax error`**
  **常见原因:关键字拼错、少了引号、少了逗号。**

  **1054 未知列**
  **SQL:`SELECT nmae FROM t`**
  **沙箱报:`no such column: nmae`**
  **常见原因:列名拼错,或者表里真没这列。**

  **1146 表不存在**
  **SQL:`SELECT * FROM tt`**
  **沙箱报:`no such table: tt`**
  **常见原因:表名错、或者没选库。**

  **1062 主键重复**
  **SQL:`INSERT INTO t VALUES (1, 'b')`**
  **沙箱报:`UNIQUE constraint failed: t.id`**
  **常见原因:主键或唯一索引撞了已有值。**

  **1048 列不能为空**
  **SQL:`INSERT INTO t (id) VALUES (2)`**
  **沙箱报:`NOT NULL constraint failed: t.name`**
  **常见原因:该列是 NOT NULL 且没有默认值。**

  **注意:上面是 SQLite 的报错文本,MySQL 的措辞不一样,但结构一样。**
  **都带错误码、都指出位置或对象名。**
  **MySQL 里对应长这样:**

  [教材原码 · 本沙箱不执行]
  | ERROR 1064 (42000): You have an error in your SQL syntax;
  |   check the manual ... near 'SELEC * FROM t' at line 1
  | ERROR 1054 (42S22): Unknown column 'nmae' in 'field list'
  | ERROR 1146 (42S02): Table 'db_database28.tt' doesn't exist
  | ERROR 1062 (23000): Duplicate entry '1' for key 't.PRIMARY'
  | ERROR 1048 (23000): Column 'name' cannot be null


  二、错误码里藏着的规律
  --------------------------------------------------------------------

  号段  谁报的                          本课的例子
  ------------------------------------------------------------------
  1xxx  服务端:SQL、数据、权限、锁     1064、1054、1146、1062、1048
  2xxx  客户端:还没摸到服务端就失败了  2003 连不上、2013 连接中断
  12xx  服务端:锁与事务                1213 死锁、1205 锁等待超时
  **第二行最好记:`2xxx` 一定是「没连上」,跟 SQL 一点关系都没有。**
  **看到 2003 就别去改 SQL 了,先查服务起没起。**

  三、读报错的四步
  --------------------------------------------------------------------
  **① 先看号段。**2xxx 查连接,1xxx 查 SQL 和数据。
  **② 再看对象名。**报错里带出来的表名、列名、键名,先拿去核对拼写。
  **③ 然后看位置。**`at line 1`、`near 'xxx'` 直接指到你写错的地方。
  **④ 最后才去搜。**把错误码加报错原文一起搜,别只搜「MySQL 报错」四个字。
  **这四步能解决日常八成的报错。剩下两成才是真需要查文档的。**
  **学好 MySQL 的方法说到底就这一条:每次报错都读到底,别直接复制去搜。**
  **读多了你会发现,报错其实是在教你它怎么想。**

五条报错里,前三条是「写错了」,后两条是「数据不合规矩」。 这两类的处理方式完全不同:前一类改 SQL,后一类改数据或者加默认值。 注意 1062 和 1048 的报错文本里都带了对象名——一个是键名 t.id,一个是列名 t.name。 报错点名的东西,就是你要去看的东西。 最后那张号段表比记五个错误码有用得多。 2 开头的错误码一定跟连接有关,跟 SQL 一个字都没关系。 知道这一条,能省掉大量「改 SQL 改到怀疑人生」的时间。 本节说的四步读法,顺序是有讲究的:先大势(号段),再具体(对象名),再看位置,最后才搜。 反过来的顺序——先搜——最容易搜到别人的环境、别人的版本,绕一大圈。

30 秒实验

  1. 把 CODE251 里的 1045 改成 1044,看分类会不会变。不变,两个都在「权限」这一类。 号段和清单这两套规则是怎么配合的,一眼就看出来了。
  2. 再给自己造一个错:给一个不存在的库建表。 会报 1049(未知数据库),属于 1xxx,归「SQL 与对象」那一类。
  3. 翻一条你以前搜过的报错,按四步读一遍。多数时候你会发现:号段和对象名已经把答案说完了。

常见误区

  1. 「报错看不懂就搜。」 先读号段和对象名,八成的错自己就能定位。 搜是最后一步,不是第一步。
  2. 「把报错整段贴进搜索框。」 带上错误码搜,命中的才是同一个问题。 整段贴进去,搜索引擎会去匹配那些无关的变量名。
  3. 「学 MySQL 就是背语法。」 语法查手册就有,值钱的是「知道什么时候该用什么」。 这个只能靠写小项目练出来。
自测三问(点开对答案) 1. 2003 和 1045 差在哪? —— **2003 是客户端根本没连上服务端,1045 是连上了但账号被拒。** **前者查服务、端口、网络;后者查用户名、密码、授权主机。** 2. 1062 属于哪一类错?该动什么? —— **属于「数据不合规矩」:主键或唯一索引撞了已有值。** **要么改插入的值,要么用 `ON DUPLICATE KEY UPDATE` 改成「有就更新」。** 3. 为什么「看计划」比「猜快慢」可靠? —— **因为 `EXPLAIN` 直接告诉你走没走索引、扫了多少行、要不要临时表。** **猜的依据通常只有「感觉」,而感觉在数据量变化后会完全失效。**

第 2 章到这儿结束。下一篇进第 3 章,讲图形化管理工具—— phpMyAdmin 怎么装、怎么用它建库建表和管理数据。

相关笔记