ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL项目实战:从环境搭建到排障调优的一线经验

MySQL项目实战:从环境搭建到排障调优的一线经验 相信打算认真做项目的人多少都经历过这样一个阶段SQL 语句会写了增删改查也能跑通可真要自己搭一个能上线的 MySQL 项目心里还是没底。这篇是 MySQL 项目开发连载的第二篇我不打算按教科书顺序把命令再讲一遍而是按我自己接项目时从装库、建表、写业务逻辑到联调、排障、上线收尾这条路把那些真正值得注意的细节集中过一遍。内容偏实战适合已经懂一点 MySQL 基础、但还没完整做过一个项目的朋友也适合在项目里被各种奇怪问题卡住、想系统排查一遍的人。1. 装库开发机的环境决策决定后面三个月省不省心很多人觉得装 MySQL 就是“下一步下一步”其实不同安装方式对应完全不同的项目阶段。装错了不是不能改但后期维护成本会高不少尤其是 Windows 上反复出现服务无法启动、端口被占这类问题根源往往在第一步。1.1 Windows 下的三种安装姿势Windows 上装 MySQL 8常见有三条路MSI 安装向导、ZIP 解压版、绿色版 exe。我在项目里最推荐的是ZIP 解压版原因很简单它暴露了 MySQL 的启动机制你能清楚知道数据目录在哪、配置文件在哪、服务是怎么注册的后面排障时不会两眼一抹黑。MSI 版适合纯开发环境点两下就能跑但它会把配置散落在系统目录和注册表里真出问题反而难查。ZIP 版步骤如下# 1. 解压到目标目录例如 D:/mysql-8.0.40 # 2. 创建配置文件 my.ini写清 basedir 和 datadir # 3. 初始化数据目录生成 root 账号 mysqld --initialize-insecure --datadirD:/mysql-8.0.40/data # 4. 注册为 Windows 服务服务名建议区分版本避免和旧实例冲突 mysqld --install MySQL8 --datadirD:/mysql-8.0.40/data # 5. 启动服务 net start MySQL8注意--initialize-insecure会生成一个空密码的 root 账号这是故意这么做的方便你首次登录后立刻设置正式密码。如果初始化时报找不到 MSVCR140.dll 这类错误先去装对应版本的 Visual C 运行库这是 Windows 下最常见的环境缺口。1.2 Linuxrpm、通用二进制、Docker 的取舍Linux 服务器上装 MySQL我分成三种场景方式适用场景优点坑rpm 包CentOS/RHEL 系systemd 集成好启动即服务升级方便依赖较多需要配好 yum 源或手动处理依赖通用二进制离线环境、定制化 Linux解压即用目录可控要自己建用户、初始化、写 systemd 配置Docker本地开发、CI、测试环境隔离干净环境变量可控删除不留痕生产环境要额外考虑数据卷和网络方案不少项目要求离线安装这时候 rpm 和通用二进制是最常用的。rpm 离线装的核心是先把依赖包准备好# 下载 mysql-community-server 及相关依赖 rpm 包 # 放在同一目录下用 yum localinstall 统一安装 yum localinstall -y mysql-community-*.rpm # 启动后初始密码会写进日志 systemctl start mysqld grep temporary password /var/log/mysqld.log通用二进制离线装更灵活适合那种连安装包源都没有的内网环境核心步骤是创建 mysql 用户、解压、初始化、自建 systemd 服务文件。很多定制化 Linux 发行版用的就是这个逻辑如果你发现systemctl start mysqld起不来先别急着怀疑包有问题看看/var/lib/mysql目录权限和 SELinux 状态这两个东西能卡住 80% 的离线安装。1.3 装完必须做的初始化五件事不管哪种方式装完我都建议立刻做这几件事不然用不了多久必然踩坑改 root 密码。MySQL 8 默认密码策略要求长度和复杂度别硬改成 123456除非你确定这只是纯本地开发库。创建业务专用账号。项目代码里不要用 root 连接单独建一个库级账号权限只给需要的那几个库。统一时区和字符集。写入my.cnf或my.ini的[mysqld]段character_set_serverutf8mb4、collation_serverutf8mb4_0900_ai_ci时区建议直接设default-time-zone08:00避免应用端连上来时间对不上。开启慢查询日志。slow_query_logONlong_query_time1这是项目上线前性能排查的依据后面调优章节还会用到。确认绑定地址。本地开发无所谓但服务器上要明确 bind-address 是127.0.0.1还是对外网卡别稀里糊涂暴露公网端口。2. 建表别急着写业务先把数据形态想清楚建表这件事看起来简单但实际上绝大多数项目的性能问题、改造成本高的问题都是在建表阶段埋下的。字段类型选错、索引乱建、字符集不统一后面每一项都要拿加班来还。2.1 字段类型你以为会了其实后半辈子都在为它还债先讲几个最容易踩的字段类型问题整数类型数量、金额计数这类int和bigint别乱用。自增主键在数据量超过 20 亿时int会溢出项目初期省那 4 个字节没必要直接bigint unsigned更省心。小数类型金额、单价、费率一律用decimal不要用float/double。浮点数在二进制下无法精确表示比如 0.1 存储后会产生误差算账时差一分钱财务能把项目打回来重做。字符串varchar长度按业务上限估不要一上来就 500、1000。MySQL 的临时表和排序对超长字段非常敏感。时间类型datetime和timestamp的选择。timestamp受时区影响存储范围到 2038 年datetime不随时区变化但如果你在项目里统一用 08:00其实差别不大。我习惯用datetime配上DEFAULT CURRENT_TIMESTAMP避免应用层再塞一次时间。默认值搜索热词里有个mysql 设置默认值为0业务表里状态位这类字段建表时就直接定好默认值别等代码里写。一个实际项目的订单表大概长这样CREATE TABLE order_info ( id bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 订单ID, order_no varchar(32) NOT NULL COMMENT 订单号, user_id bigint unsigned NOT NULL COMMENT 用户ID, amount decimal(10,2) NOT NULL COMMENT 实付金额, status tinyint NOT NULL DEFAULT 0 COMMENT 订单状态0待支付1已支付2已取消, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_created (user_id, created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT订单主表;字符集必须是utf8mb4这个不能妥协。你永远无法预知用户会输入什么稀奇古怪的字符只有utf8mb4才能完整覆盖所有 Unicode 字符。2.2 索引不是越多越好但也不是能省就省索引建得好不好直接决定项目在数据量上来之后是秒开还是卡死。我的原则只有几条主键必须用且尽量是自增或单调递增的值InnoDB 的聚簇索引特性决定了无序主键会引发页分裂。唯一键用在业务上有唯一性要求的字段比如订单号、用户手机号既约束数据又代替普通索引。联合索引遵循最左前缀原则(user_id, created_at)能同时服务WHERE user_id?和WHERE user_id? ORDER BY created_at两个场景。索引不是装饰品写操作多、数据量小的表索引过多反而拖慢插入速度。一张业务表索引数量控制在 5 个以内是比较合理的。很多人在排序慢的时候只会加索引但其实排序能不能走索引跟ORDER BY字段顺序是否和索引顺序一致有关。比如索引是(user_id, created_at)那么ORDER BY created_at单独拿出来是走不了这个索引的必须WHERE user_idxxx ORDER BY created_at才行。2.3 修改表结构的姿势alter table 也要讲顺序项目开发过程中加字段、改类型是免不了的。很多人直接一条ALTER TABLE就上了线上大表这么玩分分钟把整个库卡死。ALTER TABLE在 InnoDB 下虽然支持在线 DDL但有些操作比如修改字段类型、重建索引依然会锁表。我的习惯是先确认当前表数据量和执行时间窗口。小表直接ALTER TABLE大表用工具如 pt-online-schema-change做在线变更或者拆成批次。变更前备份结构变更后在测试环境跑一遍业务冒烟测试。新加字段尽量放表尾或显式指定位置别动不动FIRST会影响已有记录的存储布局。2.4 数据同步场景下的结构转换MySQL 到 TDengine / ClickHouse项目做大了以后MySQL 不太适合扛所有场景尤其是时序数据、海量日志分析这类。现在不少项目会把 MySQL 里的业务表同步到 TDengine 或 ClickHouse做实时报表或数据分析。以 TDengine 为例MySQL 的普通业务表要转成超级表 子表的模型把业务主键或设备标识作为标签tag把时间列作为 timestamp其余数值列作为字段。我之前用一个 Python 脚本读information_schema自动把 MySQL 表结构映射成 TDengine 的建表语句避免手工一张张建。这个转换思路比具体代码更重要不是把 MySQL 的表结构原样搬过去而是按目标数据库的时序模型重新抽象。如果项目需要 Flink 做实时同步那也是同样的逻辑MySQL 作为业务源库ClickHouse 或 TDengine 作为分析库中间用 Flink CDC 捕获变更、解析 JSON、按目标表结构写入。重点是不要试图让两端字段一一对应而是只同步分析真正需要的列。3. 事务、锁、存储过程把业务逻辑的安全边界焊死项目里一旦涉及订单、钱包、库存这类数据就必须认真理解事务和锁。很多项目跑着跑着出现数据错乱、界面卡死基本都是这一层出了问题。3.1 事务的隔离级别为什么是项目的底线问题MySQL InnoDB 默认隔离级别是REPEATABLE READ可重复读这个和很多其他数据库默认的READ COMMITTED不一样。隔离级别决定了并发事务之间能看到什么READ UNCOMMITTED脏读别人还没提交的数据你也能读到项目里基本别用。READ COMMITTED不可重复读同一条查询在事务内多次执行结果可能不同。REPEATABLE READMySQL 默认事务内多次读取结果一致通过 MVCC 实现。SERIALIZABLE最强隔离但并发性能断崖式下跌几乎不用。实际项目里不用太纠结理论只要明白一点你的业务是否能接受在同一事务里重复读到不同的值。订单支付环节一般选择默认的REPEATABLE READ就够了分析报表类的只读查询可以显式降低隔离级别减少锁竞争。-- 查看当前隔离级别 SELECT transaction_isolation; -- 事务内做多步操作时明确提交和回滚边界 START TRANSACTION; UPDATE account SET balance balance - 100 WHERE user_id 1; UPDATE account SET balance balance 100 WHERE user_id 2; COMMIT;3.2 锁表锁、行锁、死锁和锁表的现场搜索热词里mysql锁的分类mysql锁表出现频率这么高说明大家真的被这个问题折磨过。InnoDB 下主要分两类表锁LOCK TABLES显式加锁或者某些 DDL 操作引发的元数据锁。项目里尽量避免手工锁表。行锁InnoDB 通过索引对记录加锁包括记录锁、间隙锁、临键锁。间隙锁是REPEATABLE READ下防止幻读的关键但也容易引发死锁。死锁的典型场景是两个事务分别持有对方需要的资源互相等待。排查死锁我有固定动作-- 查看当前事务和锁等待 SELECT * FROM information_schema.INNODB_TRX\G; SELECT * FROM information_schema.INNODB_LOCK_WAITS\G; -- 查看最近一次死锁日志 SHOW ENGINE INNODB STATUS\G;死锁日志在LATEST DETECTED DEADLOCK段落能看到两个事务各持有什么锁、在等什么索引记录。项目里防死锁的经验是多个事务更新多条记录时固定按照同一个顺序更新比如都先更新 user_id 小的再更新 user_id 大的事务时间尽量短减少持锁时间避免在事务中做长查询、远程调用这类慢操作。3.3 存储过程用还是不用我的判断标准MySQL 存储过程在热词里一直很热门但我的态度有点复杂。存储过程适合做两类事一是数据库内部批量数据加工二是对一致性和事务边界要求极高的固定流程。但我不建议把核心业务逻辑都塞进存储过程因为代码版本管理、调试、跨数据库迁移都会变得非常痛苦。如果你的项目里确实要用我建议只放数据加工逻辑业务判断放应用层。一个典型例子是批量生成对账单DELIMITER $$ CREATE PROCEDURE generate_statement(IN p_user_id BIGINT) BEGIN INSERT INTO statement(user_id, total_amount, created_at) SELECT user_id, SUM(amount), NOW() FROM order_info WHERE user_id p_user_id AND status 1; END$$ DELIMITER ;存储过程写起来本身不难难的是维护。团队里如果只有一个人懂存储过程后面的人改起来会想哭这个成本要提前算进去。4. 联调应用端接入 MySQL 的常见姿势与翻车点数据库本身跑得再稳应用连不上、连上一会儿就断、性能拉胯项目还是起不来。这个环节的问题往往是多语言场景下各自踩各自的坑。4.1 驱动和连接池连接字符串也分三六九等应用连 MySQL 一定绕不开连接串。很多人网上一抄就用结果一堆莫名其妙的毛病。以 JDBC 为例连接串里这几个参数必须搞清楚jdbc:mysql://localhost:3306/mydb?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrueserverTimezoneMySQL 8 驱动要求显式指定时区不然报 CST 时区混乱。useSSLfalse开发环境不建证书时先关掉不然连上就可能报 SSL 连接错误。allowPublicKeyRetrievaltrue配合useSSLfalse使用不然 MySQL 8 默认的 caching_sha2_password 插件在非 SSL 情况下连不上。连接池方面Java 用 HikariCPPython 用 DBUtils 或自带的 PooledDB。连接池参数里最坑的是maxLifetime和idleTimeout不匹配导致连接被数据库端断开后应用还在用出现Connection is closed的间歇性报错。4.2 Java / Python / Android / C 四种场景的接入经验这四种是我在项目里实际用过的接入方式各有各的注意点Java Web 项目Spring Boot MyBatis数据源交给 HikariCP 管理MyBatis 里#{}和${}的区别一定要分清${}有注入风险能不用就不用。Java Web 完整案例里最常翻车的其实是事务注解没有生效记得检查Transactional是否被同一个类内部方法调用绕过代理。Python Web 项目Djangosettings.py 里配置 DATABASES注意CONN_MAX_AGE不要设太长否则 MySQL 的 wait_timeout 一断Django 还持有旧连接。Django 的 ORM 会自动处理事务但AUTOCOMMIT设置要和业务匹配。Android 项目Android Studio搜索词里有不少人在试直连 MySQL我的经验是不推荐在移动端直连 MySQL**。Android 主线程不能做网络操作而且客户端直连数据库等于把账号密码发给所有人。正确做法是后端提供 HTTP 接口Android 端只调接口。C 项目MySQL Connector/C 接入时最容易的问题是链接库版本不匹配。注意区分libmysqlclient和 Connector/C 两套 API 体系编译时把 include 和 lib 路径指对。C 直连适合做内网服务、设备端采集不太适合做高并发 Web 后端。4.3 MySQL 同步到 ClickHouse不只是搬数据项目里做数据报表时经常需要把 MySQL 业务数据同步到 ClickHouse。很多新手以为就是导 CSV其实生产级同步要考虑增量机制和目标表模型。Flink CDC 是目前比较主流的方案MySQL 开 binlogFlink CDC 解析后写入 ClickHouse。我的建议是目标表的主键和索引按查询场景设计不要照搬 MySQL 的主键。同步任务一定做幂等重复写入不能产生重复数据。同步性能瓶颈通常在 ClickHouse 的写入批量大小调大每次写入行数减少 parts 数量。5. 排障我和 MySQL 死磕过的几个经典现场说实话我在项目开发上花在排障上的时间绝对不比写业务代码少。这里把搜索热词里出现频率最高的几个故障场景拉出来按完整排查链路讲一遍。5.1 net start mysql 服务无法启动Windows 上net start mysql报服务无法启动90% 是这三类原因按顺序排查数据目录没初始化或权限不对。mysqld --initialize-insecure没有执行或者 data 目录路径和 my.ini 不一致。最容易犯的错是 my.ini 里写了datadirD:/mysql/data但实际目录不在这里。my.ini 配置项写错。比如路径用了中文、反斜杠没有转义。检查basedir和datadir是否真实存在。端口或已有实例冲突。3306 被占用或系统里已经装过另一个 MySQL 服务。排查方法很直接先看 Windows 事件查看器里的 MySQL 日志再手动跑一次mysqld --console错误信息会直接打在控制台里比瞎猜快得多。5.2 SSL 连接错误一个让人头秃的隐藏坑mysql ssl 连接错误在热词里长期霸榜说下最常见的场景客户端工具Navicat、DBeaver和 JDBC 连接 MySQL 8 时报 SSL 相关错误或Public Key Retrieval is not allowed。为什么会出现这个问题MySQL 8 默认使用caching_sha2_password认证非 SSL 连接下需要先取回 RSA 公钥才能做密码传输客户端如果不启用allowPublicKeyRetrieval就会失败。开发环境下最简单的解法是连接串里加useSSLfalseallowPublicKeyRetrievaltrue或者给 MySQL 配好证书走真正的 SSL。需要注意的是生产环境直接关 SSL 是有安全风险的做法但如果内网有防火墙、数据不敏感很多项目也这么干。线上环境我更建议把证书配好毕竟 MySQL 明文传输的账号密码一旦被截获整个库都危险。5.3 报错信息里的玄机先看日志最上面热词里有mysql e0434352、[ERROR] [MY-014060] [SERVER] invalid mysql server upgrade这类看起来像乱码的报错。我的经验是大多数 MySQL 报错真正的根因都不在最表面那行错误码而在日志文件里更早出现的上下文。e0434352这类 Windows 异常码通常对应的是 .NET 程序或 MySQL 客户端组件加载失败先确认 Visual C 运行库、.NET Framework 版本再检查 MySQL Connector 位数是否和程序一致。[MY-014060]系列升级报错说明当前数据目录版本和mysqld版本不匹配。常见于用新版程序启动旧数据目录或者降级后直接启动。解决方式是备份数据目录用匹配版本的mysqld做升级启动而不是强行跳过检查。5.4 常用命令速查关键时刻别上网查排障时最忌讳临时查文档这些命令我建议直接背下来-- 查看连接和进程 SHOW PROCESSLIST; -- 查看关键状态 SHOW GLOBAL STATUS LIKE Threads_connected; SHOW GLOBAL STATUS LIKE Slow_queries; -- 查看当前事务 SELECT * FROM information_schema.INNODB_TRX\G; -- 查看表结构 SHOW CREATE TABLE table_name\G;6. 调优和面试上线前做对的几件事顺便把试考了项目要上线性能总得过关。而 MySQL 调优这件事越早做越省钱。而且我越来越发现面试题里问的那些东西其实就是真实项目里天天要用的东西。6.1 慢查询和 EXPLAIN 是调优的基本盘开篇时我说要开慢查询日志线上跑几天之后翻日志基本能知道系统卡在哪。拿到一条慢 SQL第一件事就是EXPLAINEXPLAIN SELECT user_id, amount FROM order_info WHERE user_id100 ORDER BY created_at DESC\G;看几个关键列就够type是否从ALL变成了ref/rangekey是否真正用到了你建的索引rows预估扫描多少行Extra里有没有Using filesort或Using temporary。这两个 Extra 出现基本代表这条 SQL 有优化空间。6.2 排序的坑filesort 和索引排序排序问题在热词里专门有一条mysql排序确实值得单独讲。MySQL 排序有两种路径一种是直接用索引顺序不额外排序另一种是Using filesort也就是数据量小的时候在内存排数据量大的时候落磁盘排。ORDER BY想走索引条件是排序字段和索引方向一致并且WHERE条件里的等值字段必须是索引最左前缀。如果你的 SQL 无论如何都走不了索引排序可以考虑在应用层做排序比如把数据量控制到几千条内再排效果反而更好。6.3 MySQL 面试题和真实项目的对应关系搜索热词里mysql面试题热度一直居高不下这里我把常见面试题和最实在的项目经验对应一下事务隔离级别对应第 3 节讲的事务边界面试问的是概念项目里问的是什么时候该改隔离级别。索引失效WHERE user_id1100、LIKE %xxx这类写法不走索引项目里写 SQL 时就要避开。MVCC 原理理解可重复读是怎么实现的你才能解释为什么同一个事务里两次查询结果一样但更新时却可能碰到锁冲突。连接池参数被问连接池大小设多少时不背公式直接说按机器核数和业务耗时估算并给出理由。这些知识不是分离的而是同一个项目的不同侧面。面试时能把你做过的项目里这些细节讲清楚比背一百道题都管用。最后再分享一个我自己常犯过的错误项目初期数据量小总觉得性能优化是以后的事结果表结构和 SQL 写法从一开始就没按规范来等数据量翻了几倍才发现问题全都挤在一起。建议从第一个建表语句开始就把字符集、索引、事务边界、连接串这些基础关卡守好。MySQL 给人的回报是很线性的——你前期省下的那点时间后面大概率会加倍还回去反过来也一样。
返回列表