前言
MySQL5.7 比 MySQL5.6 多了个 mysqld.sock.lock
这个是做什么的,解决了什么问题?
注意了,默认值使用的名称是 mysql.sock,mysqld.sock 是我修改过的名字,本文大多数文件名都直接使用我自己定义的文件名。有疑问的,请用 mysql.sock 和 mysql.sock.lock 到搜索引擎搜索。
在知道 mysqld.sock.lock 文件是做什么之前,我们应该先知道 mysqld.sock 文件是做什么的?
他是给 mysqld 数据库服务提供本地 socket 通讯的套接字文件。
什么时候需要用到这个 socket 文件?
MySQL 中我们常用的有两种连接数据库的方式,分别是:
TCP/IP
mysql -uyouruser -pyourpassword -P3306 -h127.0.0.1
mysql -uyouruser -pyourpassword -P3306 -h192.168.199.198
Socket
mysql -uyouruser -pyourpassword --socket=/tmp/mysql.sock
mysql -uyouruser -pyourpassword -hlocalhost
mysql -uyouruser -pyourpassword
# 以上三种方式是等价的,默认不填时,默认--host=localhost,--socket=/tmp/mysql.sock
MySQL 的 socket 方式连接数据库本质上是一种本地连接,只能本地登录,它区别于 TCP/IP 连接,是另外一种连接 MySQL 的方式。
下面,我们一起来观察这两种连接方式的区别
TCP/IP
登录方式

底层连接的方式

Socket 登录方式

底层连接的方式

MySQL 服务启动后,我们 rm -f mysqld.sock
文件并不会导致服务器挂掉,也不会导致 TCP/IP 方式的现有连接断掉,只是无法 socket 方式登录了。重启数据库后会重新生成 mysqld.sock 文件。(潜台词,问题不大,不用跑路~)
既然这样,mysqld.sock 好像也不是很重要,那么我们现在回来研究一下 mysqld.sock.lock 是做什么的。网上的说法是,mysqld.sock.lock 他防止其他进程复用或者覆盖他的 mysqld.sock 文件,所以 mysqld.sock.lock 实际上是服务于 mysqld.sock 的守护神。
mysqld.sock.lock 虽然他不能防止 rm -f mysqld.sock
,但他能防止别的程序使用或覆盖他的 mysqld.sock 文件。而 MySQL5.6 版本并没有 mysqld.sock.lock 的存在,所以 MySQL5.7里,mysqld.sock.lock 的引入算是一个功能增强了。实际上这个需求来源于一个 bug fix request。
https://bugs.mysql.com/bug.php?id=37629

这个 bug 最早可以追溯到 2008 年 6 月 25 日,有人提出 5.0、5.1 版本有一个 bug,他会导致:
监听端口不同的两个 MySQL 实例,共用一个 mysql.sock,容易导致用户访问到了错误的实例 由于他们共用了一个 mysql.sock,shutdown 任何一个实例时,可能导致关闭错误的实例
测试
我测了一下,这个 bug 在 5.6 版本也是存在的。
验证一: 容易导致用户访问到了错误的实例
如图,我起了两个实例,分别监听在 26146 和 26147 端口,为了方便,下面我称他们为 26146(node1) 和 26147(node2) 实例。

现在,我要把两个实例都关闭了,并且修改他们的 socket 名称为相同
[client]
socket = /tmp/mysql_sandbox.sock
[mysqld]
socket = /tmp/mysql_sandbox.sock
更可能的情况,不是配置一样的 socket,而是忘记配置 socket,导致他们都使用了默认值路径下的 tmp/mysql.sock
启动 26146 实例,如图

我们再启动 26147 实例,可以启动成功,如图

26147 实例以相同的 tmp/mysql_sandbox.sock 套接字文件启动实例成功,因为 inode 编号发生了变化,从 70 变成 75 了,所以可以确认的是,后启动的 socket 文件覆盖了前面启动的。
那很明显,如果以 socket 登录服务器的话,只能登录到后启动的 26147 实例。

情况属实,而且我认为只会访问后启动的数据库。
验证二: shutdown 任何一个实例时,可能导致关闭错误的实例
情况1
我先关闭后启动的 26147,结果如图

结论是,26147 正常关闭,但 26146 丢失 socket 文件了。(本来也不是 26146 的,他早就被覆盖掉了,正常~)
情况2
由于实际上 socket 文件是 26147 的,那么如果我去关停先启动的 26146 服务试试。

结论更吓人,这取决于服务脚本如何编写,我这个案例中,由于 26146 服务关闭时发现无法释放 socket 文件,把占用 socket 文件的服务也 kill 了。最终两个实例一起关停了。。。
情况属实,并且更糟糕。
5.7 如何防止上述事情的发生
所以,5.7 版本引入了 mysqld.sock.lock 文件,他的原理看起来是这样的:
在进程启动成功前,获取到 mysqld 的进程号,创建并写入 mysql.sock.lock 文件。 如果 mysql.sock.lock 文件已经存在,并且为空,则 mysqld 直接启动失败。(图一) 如果 mysql.sock.lock 文件已经存在,则先判断 mysql.sock.lock 文件里记录的进程号有没有被别的 mysqld 程序占用了,有则启动 mysqld 失败。如果没有,则把这个进程号覆盖写入 mysql.sock.lock 文件。(图二) 创建并把 mysqld 的进程号写入 mysqld.sock、mysqld.pid 文件,进程启动成功。
以上仅仅我个人的观察,如有不正确请与我联系。
相关截图
图一(假装是一张图)
2022-08-12T12:10:43.517525+08:00 0 [ERROR] Unix socket lock file is empty mysqld.sock.lock.
2022-08-12T12:10:43.517531+08:00 0 [ERROR] Unable to setup unix socket lock file.
2022-08-12T12:10:43.517534+08:00 0 [ERROR] Aborting
图二(假装是一张图)
2022-08-11T10:51:47.833933+08:00 0 [ERROR] Another process with pid 890 is using unix socket file.
2022-08-11T10:51:47.833945+08:00 0 [ERROR] Unable to setup unix socket lock file.
2022-08-11T10:51:47.833951+08:00 0 [ERROR] Aborting
扩展:5.6 如何防止上述事情发生呢?
低情商答案:
单实例,因为这个覆盖 socket 的问题大多数发生于 MySQL 多实例部署并且是错误的配置 socket 路径导致的。所以不使用多实例部署可以避免问题。
正常情商答案:
5.6 官方已经不维护了,升级 5.7 吧。
高情商答案:
答案就是设置不同的 mysql run user,可以发生 socket 文件的覆盖,主要原因是如果两个实例共用了相同的 run user,这样会有权限覆盖别人的文件嘛!
很多人的生产规范上都是多实例共用一个 run user,就叫做 mysql,就有发生这个覆盖 socket 文件的风险,那么我们如果用多个 run user,例如 mysql3306、mysql3307... 等等,可以获取以下优势:
多租户隔离 不同的 DBA 管理不同的实例 可以基于 user 使用 quota 做磁盘配额 其他资源有关的场景,例如
# vi /etc/security/limits.conf
mysql3306 soft nproc 16384
mysql3306 hard nproc 16384
mysql3306 soft nofile 1024
mysql3306 hard nofile 65536
mysql3307 soft nproc 16384
mysql3307 hard nproc 16384
mysql3307 soft nofile 1024
mysql3307 hard nofile 1024
防止 socket 文件覆盖(本次要说的)
以下是证明过程:
修改配置文件
# 26146 实例
[mysqld]
user = mysql26146
socket = /tmp/mysql_sandbox.sock
# 26147 实例
[mysqld]
user = mysql26147
socket = /tmp/mysql_sandbox.sock
修改文件权限
cd /root/sandboxes/multi_msb_5_6_45/node1 && chown mysql26146:mysql26146 -R *
cd /root/sandboxes/multi_msb_5_6_45/node2 && chown mysql26147:mysql26147 -R *
启动
/root/opt/mysql/5.6.45/bin/mysqld --defaults-file=/root/sandboxes/multi_msb_5_6_45/node1/my.sandbox.cnf &
/root/opt/mysql/5.6.45/bin/mysqld --defaults-file=/root/sandboxes/multi_msb_5_6_45/node2/my.sandbox.cnf &
可以看出来,后启动的 26147 实例启动失败了

相关日志
2022-08-12 12:37:05 36514 [ERROR] Can't start server : Bind on unix socket: Address already in use
2022-08-12 12:37:05 36514 [ERROR] Do you already have another mysqld server running on socket: /tmp/mysql_sandbox.sock ?
2022-08-12 12:37:05 36514 [ERROR] Aborting
把 socket 文件属主修改后,可以覆盖这个 socket 了,启动成功了

所以,不使用相同的 run user,可以防止多实例持相同的 socket 文件启动。
总结
MySQL 5.7 的 mysqld.sock.lock 是为了解决 5.6 及之前版本,因为错误配置 mysqld.sock 路径导致 mysqld.sock 文件被错误覆盖,引发生产问题的一个设计。