简介:mysql-4.1.11.tar.gz是MySQL 4.1.11数据库系统的完整源码包,采用tar与gzip压缩,适用于需要在Linux/Unix环境下部署、编译或研究早期MySQL架构的开发者与运维人员。资源共包含4541个文件,压缩包大小约21.82MB,目录结构完整;主要文件类型包括C/C++源代码(.c、.h、.cpp、.cc、.hpp)、构建配置脚本(.ac、.am、.in、.sh、.configure)、测试用例与结果文件(.test、.result、.tcl、.testout),以及Java、XML、HTML文档和平台相关编译辅助文件,可满足从源码编译到功能验证的需求。目前已吸引197人浏览学习,具有一定参考价值。该版本虽较为陈旧,但有利于理解数据库内核的基础实现,也可用于兼容特定遗留业务系统。通过分析该源码包,可深入掌握MySQL 4.1.11的模块划分、客户端与服务端实现、存储引擎接口以及多种Unix/Windows构建方式,同时附带的测试集和回归结果也为数据库内核学习或旧系统排错提供了真切素材。
1. mysql-4.1.11.tar.gz:一份值得回炉的“上古”数据库源码
mysql-4.1.11.tar.gz 这个包名对现在的开发者来说有点陌生,但在 2005 年前后它是很多网站和业务系统的底层数据库。4.1 版本是 MySQL 历史上一个分水岭:它引入了子查询、视图(勉强能用)、预处理语句(PREPARE/EXECUTE)以及真正的 InnoDB 事务支持,同时字符集体系从 latin1 一家独大变成了可切换的多字符集模型。如果你做老系统兼容、二进制安全比对、或者想研究 InnoDB 早期 B-tree 实现,这份源码比文档值钱得多——它没有后来 5.7/8.0 里那层厚厚的抽象,直接读代码就能看到索引页分裂和锁等待的真实逻辑。适合三类人:被老业务系统锁死的运维、研究存储引擎实现的开发者、以及想做源码级定制的人。这篇笔记我把编译、初始化、避坑和数据迁移都过了一遍,你照着做能少走很多弯路。
2. 源码包解剖:目录结构与老版本依赖的编译器生态
2.1 解包后先看什么:configure 脚本与关键源码目录
拿到 mysql-4.1.11.tar.gz 先别急着 ./configure,先解压并观察目录结构。我一般会执行:
tar -tzf mysql-4.1.11.tar.gz | head -40输出里你会看到 configure、configure.in、configure.ac、Makefile.am、sql/、mysys/、myisam/、innobase/ 这些路径。和现代 MySQL 源码相比,4.1 没有 CMakeLists.txt——它是纯 autotools 体系,这意味着你必须在编译环境里装好 autoconf、automake、libtool,否则 configure 脚本都没法生成。
tar -xzf mysql-4.1.11.tar.gz cd mysql-4.1.11 ls -F | grep /重点关注三个目录:
- sql/:核心服务端逻辑,包括词法解析、执行计划生成、SQL 命令分发。4.1 的优化器还非常原始,表连接顺序基本靠启发式规则,代价模型很粗糙。
- myisam/:默认存储引擎,没有事务、没有行锁,表锁粒度的代表。做性能对比时你会经常回到这里看锁实现。
- innobase/:InnoDB 引擎源码。4.1 里它还叫 innobase,没并进 storage/ 目录,代码风格和后来 5.1 之后差别很大,但 B-tree 页分裂、插入缓冲的逻辑雏形都在。
2.2 编译环境的三个硬指标:gcc、glibc、make
4.1 年代的主流编译器是 gcc 2.95 和 gcc 3.3,但现代 Linux 发行版默认是 gcc 9+,这会导致一个经典问题:新编译器对隐式声明的处理比老编译器严格得多,4.1 源码里一些老式写法在 gcc 9 下直接报 error,而不是 warning。
先检查环境:
gcc --version make --version ldd --version | head -1常见做法是装一个 gcc 4.8 或 gcc 5 作为备用编译器,配合 --with-gcc= 参数指定路径。这里踩过一个大坑:gcc 版本太高时,编译 mysys/ 目录下的文件会报storage_size之类的类型不匹配错误,很多人以为源码坏了,其实只是编译器太新。
# 安装低版本编译器(以 Ubuntu/Debian 系为例) sudo apt-get install gcc-4.8 g++-4.8 # 后续 configure 前导出 CC 和 CXX export CC=gcc-4.8 export CXX=g++-4.8参数说明:CC 和 CXX 是 autotools 体系识别的编译器变量,导出后 configure 和 make 都会优先使用你指定的编译器。
2.3 我验证过的最小依赖清单
在干净的容器或虚拟机里,需要的依赖比你想象得少。理论上只需要 gcc、make、autoconf、automake、libtool、ncurses-dev(终端客户端依赖)和一个能用的 shell。不需要 openssl-dev,因为 4.1 的 SSL 支持是可选项,而且老版本 OpenSSL 接口和现代版本完全不兼容,干脆编译时把 SSL 关掉。
# Debian/Ubuntu 环境 sudo apt-get install -y gcc-4.8 g++-4.8 autoconf automake libtool ncurses-dev注意:4.1 的官方文档说支持 Linux、Solaris、FreeBSD,但我在 Solaris 上试过一次,configure 通过了,make 却在链接阶段因为系统库差异失败。后来只在 Linux 上跑通了——如果你是做跨平台验证,建议先打死 Linux 这个基准,别一开始就挑战 BSD。
3. configure 与 make:把 4.1 编译成可用的 InnoDB 实例
3.1 configure 参数怎么选:prefix、charset 与存储引擎
configure 是编译老版本 MySQL 最关键的关卡。4.1 的默认配置把 MyISAM 作为默认引擎,但我们需要 InnoDB 事务支持,所以要显式启用。我验证过一组稳定可用的参数:
./configure \ --prefix=/usr/local/mysql-4.1.11 \ --with-extra-charsets=all \ --with-charset=latin1 \ --with-innodb \ --disable-dependency-tracking \ --with-mysqld-user=mysql \ --without-query-cache \ --without-debug \ --without-ssl逐项说明:
- --with-innodb:必选。不加这个参数 InnoDB 引擎根本不会编译进去,SHOW ENGINES 里看不到 InnoDB。很多人编译完发现没有事务引擎,就是漏了这一步。
- --with-extra-charsets=all:把附加字符集全部编进去,后续才能用 ALTER TABLE CONVERT TO CHARACTER SET 做迁移。如果只编默认 latin1,后面数据迁移时连 GBK 都不认。
- --without-query-cache:4.1 的查询缓存是一个有争议的功能,命中率低且全局锁开销大,直接关掉省心。
- --without-ssl:老版本 SSL 依赖 OpenSSL 0.9.x,现代系统基本没有,编译必炸,不如关闭。
- --disable-dependency-tracking:减少 make 阶段对头文件依赖的追踪,能省不少编译时间。
3.2 make 阶段的时间线:从编译错误到可执行文件的常见卡点
configure 通过后进入 make,这一步的耗时在当时的机器上约 20~30 分钟,现代虚拟机里 5 分钟内能跑完。但如果编译器版本不对,你会在这里遇到第一个大坑:
make 2>&1 | tee build.log常见的报错是:
mysys/mf_brkhme.cc: In function 'int strmov(...)': error: 'register' is deprecated现象是register关键字在新标准里被弃用,gcc 4.8 还能当作警告容忍,gcc 9 直接报 error。解决方式是加 CFLAGS:
export CFLAGS="-Wno-register -fpermissive" export CXXFLAGS="-Wno-register -fpermissive"参数说明:-fpermissive 让编译器把一些违反标准的写法降级为警告而不是硬错误;-Wno-register 屏蔽 register 弃用警告。这两项是我回编译老 C++ 代码的保命组合。
make 成功后检查关键产物:
ls -l sql/mysqld ls -l client/mysql如果这两个文件存在且权限正确,编译阶段就结束了。使用 file 命令确认可执行格式:
file sql/mysqld输出 ELF 32-bit 或 64-bit 都正常,但如果你在 64 位系统上编译出了 32 位二进制,后续内存参数设置会受限,建议 --enable-64bit 显式指定。
3.3 验证二进制:mysql_client_test 与手动 smoke test
编译完成不代表能跑。我习惯先做一次快速的 smoke test,不急着 install。手动初始化数据目录并启动:
mkdir -p /tmp/mysql-test ./sql/mysqld --no-defaults \ --datadir=/tmp/mysql-test \ --socket=/tmp/mysql-test.sock \ --skip-networking \ --user=$(whoami) &逻辑说明:--no-defaults 表示不读取任何配置文件,--skip-networking 关闭 TCP 端口,只监听 socket,这样避免端口冲突,也方便快速验证二进制是否能启动。如果这个命令能持续运行而不是闪退,说明编译出来的核心没问题。
再做一个最简单的 SQL 验证:
./client/mysql --socket=/tmp/mysql-test.sock -e "SELECT VERSION();"能输出 4.1.11 就说明 mysqld 和客户端都活着。这一步过了再谈安装。
4. 初始化与启动:mysql_install_db 与 my.cnf 的 4.1 时代写法
4.1 初始化到底做了什么:权限表与数据目录
make install 之后,别直接启动 mysqld——老版本不像 5.7 之后那样可以在启动时自动初始化数据目录。4.1 需要手动执行 mysql_install_db 来创建系统库:
sudo make install cd /usr/local/mysql-4.1.11 sudo mkdir -p /var/lib/mysql-4.1 sudo chown -R mysql:mysql /var/lib/mysql-4.1 sudo ./bin/mysql_install_db \ --basedir=/usr/local/mysql-4.1.11 \ --datadir=/var/lib/mysql-4.1 \ --user=mysql执行完后检查数据目录:
sudo ls -la /var/lib/mysql-4.1你会看到 mysql/ 子目录,里面有 user、db、host 等表的 .frm 和 .MYD 文件。这些就是权限表,mysql.user 表里初始有两个 root 账号,一个允许本机 socket 登录,一个允许 localhost TCP 登录,密码默认是空的。
4.2 my.cnf 的最小可用配置与 key_buffer 参数
4.1 时代 my.cnf 的写法跟现在很不一样:没有 [mysqld] 下的 innodb_buffer_pool_size(那个参数要 5.0 之后才有),InnoDB 缓冲区参数叫 innodb_buffer_pool_size 的前身——在 4.1 里它叫 innodb_buffer_pool_size 但实际单位是字节,而且默认只有 8MB。写一个最小配置:
[mysqld] basedir = /usr/local/mysql-4.1.11 datadir = /var/lib/mysql-4.1 port = 3306 socket = /var/lib/mysql-4.1/mysql.sock user = mysql skip-bdb skip-federated key_buffer = 64M table_cache = 256 max_connections = 100 innodb_data_file_path = ibdata1:32M:autoextend innodb_buffer_pool_size = 128M参数说明:
- key_buffer:决定 MyISAM 索引缓存大小,4.1 里这是最重要的内存参数。设置为物理内存的四分之一左右即可。
- innodb_buffer_pool_size:InnoDB 数据页缓存,4.1 里默认只有 8MB,做任何实际测试前至少调到 128MB。
- innodb_data_file_path:必须用 autoextend,否则 InnoDB 表空间写满 32MB 后就报错,新手经常在这翻车。
4.3 连接不上时的排查顺序:socket、端口、进程日志
老版本没有 performance_schema,也没有 information_schema,排错完全靠日志和经验。我排查时按这个顺序来:
ps aux | grep mysqld第一步确认进程存在。第二步看日志:
sudo tail -50 /var/lib/mysql-4.1/*.err4.1 的错误日志默认写在数据目录下,文件名是主机名.err。如果进程起来了但客户端连不上,先看 socket 路径是否一致:
sudo ./bin/mysql --socket=/var/lib/mysql-4.1/mysql.sock -uroot如果报Can't connect to local MySQL server through socket,通常是 socket 路径不一致或进程根本没起来。如果报 Access denied,说明 root 密码已有变化,用 --skip-grant-tables 启动再重置。
sudo ./bin/mysqld_safe --skip-grant-tables --skip-networking & sudo ./bin/mysql -uroot跳过权限表登录后执行:
UPDATE mysql.user SET Password=PASSWORD('newpass') WHERE User='root'; FLUSH PRIVILEGES;这一点很重要:4.1 的 user 表里 Password 字段是 4.1 风格的 41 字节哈希,和 5.7 之后完全不同,重置密码时用 PASSWORD() 函数而不是手动填哈希。
5. 老版本避坑:编译、字符集、InnoDB 的五个典型翻车现场
5.1 configure 报错却无日志:config.log 的最后 50 行才是真相
现象:执行 configure 到某个检测环节直接中断,终端只显示一句话,没有具体原因。
原因:autotools 把检测失败信息写进 config.log,终端默认不输出。
解决:不要反复重跑 configure,先看 config.log 尾部:
tail -100 config.log | grep -A20 "error"我遇到过一次是链接测试程序失败,原因是缺 libcurses。装上 ncurses-dev 之后 configure 顺利通过。从那以后我养成了习惯:任何 autotools 项目 configure 失败,先看 config.log 再下结论。
5.2 make 死在 yacc/lex 阶段:bison 版本太新
现象:make 到 sql/ 目录时停在sql_yacc.cc报错,提示语法冲突或 undefined rule。
原因:4.1 的语法分析器需要老版本 bison 生成的解析表,bison 2.x 生成的文件和现代 bison 3.x 不兼容。
解决:安装老版本 bison:
sudo apt-get install -y bison=2.7*如果系统源里没有,就下载 bison 2.7 源码手动编译。装完清掉之前生成的 yacc 相关文件再重新 make:
make clean make5.3 字符集乱码:4.1 的 utf8 不是现在的 utf8
现象:从 4.1 导出数据,用现代 MySQL 导入后中文变成问号,或者反过来。
原因:4.1 的 utf8 只支持基本多语言平面,每个字符最多 3 字节,不包含 emoji 和冷僻汉字;而且 4.1 的 utf8 排序规则和 5.7、8.0 也不完全一致。
解决:导出时显式指定字符集,导入时也显式指定:
mysqldump -uroot -p --default-character-set=utf8 --compatible=no_keywords olddb > dump.sql导入前在目标库先设置会话字符集:
mysql --default-character-set=utf8 targetdb < dump.sql5.4 InnoDB 启动崩溃:表空间文件损坏与 autoextend
现象:mysqld 启动时报 InnoDB 错误,提示ibdata1尺寸不匹配或 log 文件大小不一致。
原因:4.1 的 InnoDB 在非正常关机时 recovery 能力弱,尤其是日志文件和表空间文件不一致时直接拒绝启动。
解决:把 innodb_force_recovery 从 1 开始逐级试:
[mysqld] innodb_force_recovery = 1每次启动后立刻用 mysqldump 导出数据,然后重建数据目录。4.1 的 force_recovery 到 6 级时只读不写,能救多少算多少。
5.5 root 密码失效:禁用了 root 的远程权限
现象:本地 socket 能登录,但通过 TCP 用 root 连接时报 Access denied。
原因:4.1 默认只在 user 表里放了两条 root 记录,host 是 localhost 和 127.0.0.1。没有 host 为 '%' 的记录。
解决:插入一条允许本机网络登录的记录,但别开外网 root:
CREATE USER 'root'@'127.0.0.1' IDENTIFIED BY 'newpass'; GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION; FLUSH PRIVILEGES;6. 数据迁移实战:把 4.1 的数据安全搬到现代 MySQL
6.1 mysqldump 的版本兼容边界与 --compatible 参数
4.1 导出的数据直接灌进 8.0 大概率失败,原因集中在三处:语法结构差异、字符集定义方式、以及索引长度限制。一个可靠流程是:先用老版本 mysqldump 导出,再做文本级修正。
/usr/local/mysql-4.1.11/bin/mysqldump \ -uroot -p \ --default-character-set=utf8 \ --opt \ --quote-names \ --skip-lock-tables \ olddb > olddb_dump.sql参数说明:--opt 是 --quick --add-drop-table --add-locks --extended-insert 的合体,导出速度更快;--quote-names 让表名和字段名带上反引号,避免新版本把老库的关键字当语法错误。
导出后检查文件里有没有 TYPE=MyISAM 或 TYPE=InnoDB 这种老写法,新版本只认识 ENGINE=:
sed -i 's/TYPE=InnoDB/ENGINE=InnoDB/g' olddb_dump.sql sed -i 's/TYPE=MyISAM/ENGINE=MyISAM/g' olddb_dump.sql6.2 字符集迁移的后悔药:从 latin1 到 utf8mb4 的完整步骤
这是很多老库换新库最痛的点。4.1 默认 latin1,但很多业务里实际存的是 UTF-8 字节流——因为当年客户端连接时手动 SET NAMES utf8,数据本身是 UTF-8,只是列定义是 latin1。直接转 CONVERT TO CHARACTER SET 会双重转换把数据改坏。
正确的做法是先确认列里实际字节编码:
SELECT HEX(LEFT(title, 10)) FROM news LIMIT 5;如果十六进制里能看到 E4 B8 AD 这种 UTF-8 三字节序列,说明数据本身是 UTF-8,不要做列转换,只要改列定义:
mysql -uroot -p --default-character-set=utf8 olddbALTER TABLE news MODIFY title TEXT CHARACTER SET utf8mb4;注意:这里没写 CONVERT TO,只用 MODIFY 改列属性,数据字节没有被动过。之后再修改客户端连接规则:
mysql -uroot -p --default-character-set=utf8mb4 olddb如果数据里混着真正的 latin1 编码(比如 À、é 这种单字节),才需要先转成二进制再转:
ALTER TABLE news ADD COLUMN title_backup TEXT; UPDATE news SET title_backup = CONVERT(CAST(title AS BINARY) USING utf8mb4);操作前务必全库备份,多留一条后悔药。做完迁移后用两个指标验证:COUNT(*) 行数一致,以及关键字段的 MAX(LENGTH(field)) 和 MAX(CHAR_LENGTH(field)) 比例接近 3:1(说明中文字符没有丢失)。
从那以后我每次做老版本 MySQL 迁移,都强制走一遍「先看真实编码、再改列属性、最后改连接方式」的顺序,不在任何一步用默认选项。希望帮到你。
本文还有配套的精品资源,点击获取