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

MySQL 不是文件,是一套【客户端/服务端】程序。 这一条不通,后面所有「连不上」「为什么改完别人看不到」都解释不了。 它换过三次东家,才有了今天的 MariaDB 分支。 看懂版本号的三段数字,才知道升级到底在冒什么险。

📝 本章和前面几章的关系

第 1 章讲的是通用的数据库原理,这一篇起开始讲 MySQL 这个具体产品。 它和第 4 章的分工:这一篇讲「MySQL 是个什么样的东西」, 第 4 章讲「怎么在里面建第一个库」。 这一篇的「客户端/服务端」是全书的地基。 第 24 章讲 PHP、Python、Java 怎么连数据库时,连的就是这一篇讲的那个服务端。

一、它是软件,不是文件

第一次见 MySQL,最容易错的是把它当成一个「文件」——像 Excel 那样点开就能看。 它不是。它是一个服务进程守着端口,谁要数据谁来连。 这叫【客户端/服务端】结构。 这件事有三个直接后果,后面每一章都会碰到。

后果 具体表现
数据只有一份 在服务端的数据目录里,客户端什么都不存
能同时给很多人用 并发由服务端统一调度,不靠「我这边先看过了」
服务一停谁都读不到 不是文件坏了,是没有进程在应答

所以「装个 MySQL」装的是服务端。 你写的程序、图形工具、命令行窗口,全都是客户端。 图形工具和命令行有什么区别? 没有区别,它们发的都是同一种 SQL,走同一条连接。 这点在下一篇讲 8.0 新特性时会再撞上:工具换了,语法不变。

二、三次易主:从 MySQL AB 到 Oracle

教材讲发展史只用了一小段,但那段里有个关键线索:MySQL 换过三次东家。

时间 归谁 发生了什么
1995 MySQL AB(瑞典) 公司成立,MySQL 从这里起步
2008 Sun Microsystems Sun 以十亿美元收购 MySQL AB
2010 Oracle Oracle 收购 Sun,MySQL 跟着过去

社区真正担心的不是「换老板」,是「换成一个卖商业数据库的老板」。 于是原作者把同一份代码分了出去,起了个名字叫 MariaDB。 今天说「MySQL 分支」,至少有三个来源: Oracle 的 MySQL、MariaDB、Percona Server。 三者语法高度兼容,但 8.0 之后分叉越来越大,别混着用。 担心归担心,MySQL 到现在还在发版。 判断一个版本还能不能用在生产上,只看一件事:它在不在支持期内。

三、它凭什么流行

教材列了 MySQL 的优势,那些理由今天依然成立,但权重变了。

理由 说人话
开源且免费 社区版走 GPL,公司不用为它批一笔预算
装起来轻 一台两核四 G 的机器就能跑真业务
生态最全 每种语言都有驱动,每家云都有托管版
资料最多 报错搜得到,教程一抓一大把
够用 中小项目的数据量和并发,它扛得住

五条里最容易被高估的是「免费」,最容易被低估的是「生态」。 免费只是门槛低;生态是——遇到任何问题都有人踩过,任何语言都接得上。 但「够用」有边界,得说清楚: 单表上亿行、写请求上万 QPS,就不是加机器能解决的了。 那是 2.3 节要讲的主从与分片,也是第 24 章项目实战的背景。

四、版本号里藏着什么

教材让你装 8.0,但下载页上一堆版本号,先得看懂那三段数字。 主版本.次版本.补丁号:5.7.32 里 5 是主、7 是次、32 是补丁。 主版本变是大改,可能不兼容;次版本变是加功能;补丁号变只修 bug。

VERS = ["5.7.32", "8.0.36", "8.4.2"]
print("版本号    主  次  补丁   这个分支的定位")
print("-" * 52)
for v in VERS:
    a, b, c = [int(x) for x in v.split(".")]
    if a == 5:
        tag = "老世代,2023 年 10 月停服"
    elif b == 0:
        tag = "经典 8.0,长期支持"
    else:
        tag = "创新版,滚动升级"
    print("%-9s %2d  %2d  %3d   %s" % (v, a, b, c, tag))

跑出来三行,说的其实是三件事: 5.7 已经是老世代,2023 年 10 月停止支持,新项目没有理由再选。 8.0 是长期支持版,改 bug 不改接口——生产环境的标准选择。 8.4 是创新版,功能新但生命周期短,适合试新东西,不适合放着不管的业务。 所以「升级」分两种,风险完全不同: 打补丁(8.0.35 到 8.0.36)风险极低,跨主版本(5.7 到 8.0)要专门做兼容测试。

五、动手 Demo:客户端连服务端:一个进程、一个端口、一群临时会话

这一课用一个真的 socket 服务器把「客户端/服务端」演出来。 它当然不是 MySQL,但连接模型一模一样。 四段:起服务、三个客户端轮流连、把服务关掉看报错、最后把这种结构的账列清楚。

import socket

srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.bind(("127.0.0.1", 0))
port = srv.getsockname()[1]
srv.listen(8)

hits = []
def serve():
    while True:
        conn, addr = srv.accept()
        with conn:
            data = conn.recv(1024).decode("utf-8").strip()
            hits.append(data)
            conn.sendall(("OK %d: 收到 [%s]"
                          % (len(hits), data)).encode("utf-8"))

for name in ("客户端A", "客户端B", "客户端C"):
    c = socket.create_connection(("127.0.0.1", port))
    c.sendall(("%s 查询 1 号商品" % name).encode("utf-8"))
    print(c.recv(1024).decode("utf-8"))
    c.close()

实跑输出:

--------------------------------------------------------------------
第四课:MySQL 是「一个服务端 + 很多客户端」,不是一个文件
--------------------------------------------------------------------

  **很多人第一次接触数据库,以为它像 Excel 一样是个文件,点开就能看。**
  **不是。MySQL 是典型的【客户端/服务端】结构:一个进程守着端口,所有程序都去连它。**
  **这一课用一个真的 socket 服务器把这个结构演出来。**
  **它当然不是 MySQL,但连接模型一模一样。**

  一、服务端起在端口上,等别人来连
  --------------------------------------------------------------------
  **服务器 bind 到 127.0.0.1:58786,然后 listen——它在等,不主动找谁。**
  **注意这台「服务器」自己不认识任何数据,它只负责接话、回话。**
  **真正的 MySQL 也是这样:数据在它的数据目录里,客户端什么都存不住。**

  二、三个客户端各自连一次
  --------------------------------------------------------------------
  **客户端A 连上、发一句、收到「OK 1: 收到 [客户端A 查询 1 号商品]」、断开。**
  **客户端B 连上、发一句、收到「OK 2: 收到 [客户端B 查询 1 号商品]」、断开。**
  **客户端C 连上、发一句、收到「OK 3: 收到 [客户端C 查询 1 号商品]」、断开。**
  **三次连接互不相干,服务器那边的计数却在累加。**
  **这就是「状态在服务端,客户端只是临时会话」。**

  三、把服务端关掉,客户端会怎样
  --------------------------------------------------------------------
  **服务器一关,客户端立刻连不上,报的是 `timed out`。**
  **这台沙箱把「没人听的端口」一律报成超时;真机上更常见的是【积极拒绝】(WinError 10061)。**
  **两种说法都是一个意思:那个端口上没有进程在应答。**
  **换成 MySQL,这就是那句最常见的:**
  `ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (10061)`
  **所以「数据库连不上」先查两件事:服务起没起、端口通不通。**
  **不是先怀疑密码。**

  四、这个结构带来什么
  --------------------------------------------------------------------

  现象            因为它是 C/S                   所以你该怎么做
  --------------------------------------------------------------------------------------
  数据只有一份    数据在服务端,客户端不存        备份在服务端做,别去拷客户端的文件
  多人同时用      服务端统一调度并发              靠事务和锁,不靠「我这边先看过了」
  连不上就用不了  网络、端口、进程任一断了都不行  先 ping 通、再 telnet 端口、最后看进程
  能限速、能限权  请求都经过服务端                权限在服务端授,客户端改不了
  升级只升一处    逻辑在服务端                    客户端程序不用跟着换
  **最后一行值得多说一句:这就是为什么 MySQL 5.7 升 8.0,Java 程序一行不用改就能跑。**
  **逻辑压在服务端,客户端只发 SQL、收结果——这也正是 1.3 节讲的「两级映射」在工程上的样子。**

第一段只有一句话值得记:服务器 bind 到端口之后是 listen,它在等,不主动找谁。 MySQL 服务起来之后干的就是这件事。 第二段能看出「状态在服务端」:三个客户端各连一次、各发一句,每个客户端都不知道自己是第几个, 但服务器那边的计数在累加。 会话是临时的,状态是持久的——这一条决定了「换台电脑登录,数据还在」。 第三段是最有用的一段。 服务一关,客户端立刻连不上,报的错跟「密码错」完全是两回事。 换到 MySQL 就是那句 ERROR 2003。 所以「数据库连不上」先查服务起没起、端口通不通,别一上来就怀疑密码。 最后那张表列了这种结构的五笔账。 最值得注意的是最后一行:逻辑都在服务端,所以 MySQL 5.7 升 8.0,Java 程序可能一行都不用改。 这也是为什么数据库的兼容性比任何框架都金贵。

30 秒实验

  1. 把 Demo 里那个服务器改成「回一句就退出循环」,再连三次。 第二次连接会卡住——因为队列里没人接,客户端在等。 这就是「服务假死」的样子:端口通,但不干活。
  2. 想一个你天天用的服务,判断它是「有状态的」还是「无状态的」。 网页服务器多半无状态(每次请求重来),数据库一定有状态(连上之后会话变量还在)。 这就是为什么数据库难做横向扩展。
  3. 写一行 SELECT VERSION();,再想想:为什么客户端问版本要发一条 SQL,而不是自己读文件? 因为客户端什么都不知道,全部信息都在服务端。

常见误区

  1. 「MySQL 就是个文件,拷走就带走了数据。」 直接拷数据目录确实能带走,但必须先停服务再拷。热拷出来的文件可能一半是新内容一半是旧内容, 恢复时报错。
  2. 「被 Oracle 收购了,所以不能商用。」 社区版走 GPL,商业项目照样能免费用(改了自己产品的源码才要开源)。 真正要花钱的是企业版和支持服务,那是另一回事。
  3. 「版本号越大越好。」 8.4 是创新版,8.0 是长期支持版。 生产环境选在支持期内的长期支持版,图那个「只修 bug 不加功能」的稳定。
自测三问(点开对答案) 1. 装到机器上的那部分,是客户端还是服务端? —— **服务端(mysqld)。** **客户端是另外的程序**:命令行 `mysql`、图形工具、你写的 Java 程序,都是客户端。 2. 5.7.32 和 8.0.36 的差别在哪一段数字上?意味着什么? —— **差在主版本(5 对 8)。** **主版本变意味着大改,可能不兼容**——这也是 5.7 升 8.0 要先做兼容测试的原因。 3. 为什么「数据库连不上」不该先怀疑密码? —— **因为报错号段不一样:密码错是 1045(服务端拒绝),** **连不上是 2003(客户端根本没摸到服务端)。** **看到 2 开头的错误码,先去查服务和端口。**

下一篇讲 8.0 到底带来了什么。 教材列了一长串新特性,真正改变写法的只有三样——窗口函数、CTE、降序索引,其余都是「更好用」。

相关笔记