多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

MySQL实战指南:安装、事务、索引与高并发优化

MySQL实战指南:安装、事务、索引与高并发优化 最近社区里有人喊“MySQL 被干成老二了”作为一个天天和数据库打交道的开发者听到这句话先是愣了一下然后忍不住看了看手里的饭碗。MySQL 确实在不少榜单上从曾经的“开源一哥”位置滑落比如在 DB-Engines 的排名里常年被 Oracle 压着偶尔还要和 PostgreSQL 争个脸红脖子粗。但“老二”这个说法放在数据库领域并不丢人——尤其当 MySQL 的安装量、生态成熟度、招聘需求量依然是开源数据库里的顶流时它对中小团队和互联网业务依然是第一选择。这篇就来聊聊MySQL 到底“老二”在哪以及我们怎么把这位老二用得比老大还顺手。1. 说说“老二”这个事1.1 从榜单看MySQL的座次先看最直观的排行榜。DB-Engines 网站每月都会按搜索引擎收录、招聘信息、社区讨论等维度给数据库打分MySQL 常年排在第二位仅次于 Oracle。很多同学一看这排名就慌了觉得 MySQL 是不是不行了。但请注意这个榜单统计的是“关注度”不是“实际使用量”。MySQL 在 Web 应用、云原生场景里的部署量依然非常夸张隔壁的 PostgreSQL 虽然社区活跃但论存量业务、中间件兼容性、运维工具链的成熟度MySQL 还是实打实的核心选手。具体到国内大部分中小公司的业务系统、SaaS 平台、内容站开箱即用的默认数据库基本就是 MySQL。你要说它被“干成老二”那这个“老大”是谁如果是 Oracle那 MySQL 从来没当过老大如果是 PostgreSQL那也只是在某些新项目上被抢走了“默认首选项”。真实环境里如果一个 Java 后端岗位要求候选者熟悉 MySQL这依然是硬通货。1.2 真正威胁MySQL的是什么抛开情绪说说 MySQL 真正要面对的挑战。第一个是功能迭代节奏。PostgreSQL 在索引类型、JSON 支持、全文检索、外部数据源这些特性上走得很快很多新项目为了“少踩坑”直接选了 PostgreSQL。第二个是云厂商的“默认项”问题。某些云数据库平台把 PostgreSQL 作为默认推荐导致新用户没得选。第三个是 MySQL 自身的坑比如默认字符集不是 utf8mb4、非严格模式下的数据截断不太透明、8.0 之前的锁粒度控制不了那么多这些都在舆论上给 MySQL 减分。但你要是真的去问那些在系统里跑了五六年 MySQL 的团队他们最大感受不是“被迫当老二”而是“MySQL 的稳定性和生态让人不敢轻易迁移”。中间件、监控、备份工具、binlog 解析、主从切换方案早就是一套成熟流水线。所以我的判断很明确MySQL 的“老二”身份更多是宣传战的产物它可能不是最新潮的但绝对是最可靠的乘法器之一。2. 与其焦虑不如把MySQL玩透2.1 一份能跑的MySQL 8.0安装记录Windows 压缩包不管 MySQL 排名第几装机流程永远是新手的第一道坎。我翻了翻最近的热搜词清一色的“mysql安装教程”、“mysql在windows10上怎么安装”甚至有人卡在d:\tool\mysql-8.0.46-winx64\binnet start mysql这种命令行里。这里我把自己的完整操作流程复述一遍用的是官方免安装版因为这个版本坑最少很适合写代码的折腾。下载mysql-8.0.46-winx64.zip之后解压到你想要的目录比如D:\tool\mysql-8.0.46-winx64。然后新建一个my.ini放在根目录注意编码要存成 ANSI 或者 UTF-8 无 BOM否则配置可能解析失败。样例配置如下[mysqld] basedirD:/tool/mysql-8.0.46-winx64 datadirD:/tool/mysql-8.0.46-winx64/data port3306 character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-storage-engineINNODB接着用管理员身份打开 cmd切到 bin 目录先执行初始化命令mysqld --initialize-insecure注意我用的是--initialize-insecure这个会生成一个 root 空密码适合本地开发。初始化完成后就会在 datadir 里生成一堆系统文件和目录。然后安装服务mysqld --install MySQL8安装成功后会提示Service successfully installed。然后启动net start MySQL8如果你能在这里顺利看到“服务正在启动”并且不报错恭喜你你已经越过了大部分新手绕不过去的坎。如果报错先去看my.ini里的路径有没有写错再看端口有没有被占用。这个顺序是最稳的我试过不下十次基本都是路径和权限问题。2.2 用Docker跑MySQL的坑与技巧除了本地安装用 Docker 跑 MySQL 也是热搜常客比如“docker安装mysql失败”、“docker compose部署mysql”。Docker 方案的好处是环境隔离、团队统一、升级方便。但坑也不少我最常碰到的是容器启动成功后本地客户端怎么都连不上要么报权限要么报网络。给一段能直接用的docker-compose.yml示例version: 3.8 services: mysql: image: mysql:8.0 container_name: mysql8 restart: always environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: appdb ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql - ./conf:/etc/mysql/conf.d command: --default-authentication-pluginmysql_native_password volumes: mysql_data:启动命令很简单docker-compose up -d第一次拉镜像如果遇到failed to decode referrers index这种报错大概率是 Docker 版本太旧或者镜像源不稳。处理方式换一个可信的镜像源或者升级 Docker Desktop 到新版。容器起来之后连不上十有八九是认证插件的问题8.0 默认的caching_sha2_password在部分老客户端里不支持所以我加了一行--default-authentication-pluginmysql_native_password实测能解决大半连接问题。另外强烈建议给 MySQL 容器加上内存限制。否则在并发场景下 Dockerd 会疯狂吃喝宿主机直接卡死。我在生产环境用的参数是--memory4g --memory-swap4g这样即使业务爆炸也不会把整台机器拖垮。3. 实战中MySQL的硬核技能3.1 事务与锁别再傻傻分不清热搜里“mysql事务处理”、“mysql锁的分类”、“mysql锁表”扎堆出现。事务和锁是 MySQL 里最容易被混淆但又最核心的概念。简单说事务是业务层面的原子性保证锁是数据库内部用来协调并发访问的机制。事务需要锁来实现隔离性但锁不一定是事务触发的比如一条普通 UPDATE 也会加锁。MySQL 的锁大致分成两类表锁和行锁。表锁在 MyISAM 里用得比较多把整张表锁住导致写入串行性能很差。InnoDB 支持行锁还会根据索引的特性产生间隙锁和临键锁用来解决幻读。这个扩展得很深我就不铺开讲了但有一句话建议背下来InnoDB 的锁是加在索引上的如果你更新数据时没有走索引那行锁也会退化成表锁。实际开发中最容易被坑的是“锁表”。我在前公司遇到过一次事故某个活动脚本深夜批量更新条件字段没加索引结果把一张几百万行的业务表锁了快一个小时整个线上写入全部阻塞。事后排查问题就出在没索引。所以当你要跑批量更新或者删除之前一定先EXPLAIN一下看看语句有没有命中索引。如果条件列没有索引宁可先建索引再执行也别冒险。还有死锁问题。比如两个事务分别更新两条记录但顺序相反就很容易互相等待最后 MySQL 检测到死锁并自动回滚一边。这个错误信息是Deadlock found。解决方案很粗暴但实用把所有涉及多条记录的更新都按同一顺序进行比如按主键 ID 从小到大排序。这个习惯能躲掉 99% 的死锁。3.2 索引与SQL优化慢查询排查实录“mysql创建索引”“mysql limit用法”“mysql排序”这些都是最日常的 SQL 操作但细节里有魔鬼。先说索引。我以前犯过一个错误为了图省事给表的每个字段都建了索引结果写入慢得一塌糊涂。索引不是越多越好它会拖慢 INSERT/UPDATE因为每次写数据都要维护 B 树。正确的做法是只给查询频繁的字段建索引并且尽量建联合索引把选择性最高的字段放前面。举一个真实例子。有一张订单表查询条件是user_id和status排序是created_at。一开始只单建了user_id索引每次查询都会先过滤用户再在结果里排序导致filesort很慢。后来把索引改成了(user_id, status, created_at)排序阶段就直接走索引的有序性性能翻了 6 倍。再说limit分页。很多人写分页喜欢直接用limit 100000, 20MySQL 会先扫出前面十万条再扔掉慢得离谱。我的习惯是改成“书签”方式比如记录上一页的最后一个 IDSELECT * FROM orders WHERE id #{last_id} ORDER BY id ASC LIMIT 20;这样能直接走主键索引速度稳定。如果你实在需要跳页可以先用子查询取出主键范围再关联原表比如SELECT o.* FROM orders o JOIN (SELECT id FROM orders WHERE status 1 ORDER BY id LIMIT 100000, 20) t ON o.id t.id;这个写法在数据量大时依然能跑得动。慢查询排查方面我一般三步走先打开慢查询日志采样十分钟再拿日志里的慢 SQL 去EXPLAIN看执行计划最后针对typeALL或者rows特别大的语句做索引优化。注意EXPLAIN里显示Using temporary或者Using filesort就是典型需要优化的信号。3.3 存储过程与函数什么时候值得用“mysql存储过程”、“mysql函数大全及举例”在热搜里占了不少位置。有人觉得存储过程是老古董但我觉得它在特定场景下依然有存在价值。存储过程的优势是减少网络往返、集中管理逻辑、可以复用复杂计算。劣势是调试困难、不好方便地做版本管理、数据库迁移成本高。我的经验是存储过程适合用在数据合规校验、报表统计、定时批处理这类逻辑稳定、很少改动的场景。比如我在做一个财务系统时每个月的结算逻辑很长几十行 SQL 加循环如果写在应用层代码会特别乱。后来改写成存储过程应用端只需要一句CALL monthly_settle();清爽很多。但存储过程也有雷区。比如里面如果涉及到大量事务操作一定要设置合理的timeout否则一旦锁冲突整个存储过程卡住连会话都会被拖死。还有个习惯问题不要在存储过程里把SELECT *的结果拼出来返回给应用端这样会让结果集难以预测还容易爆内存。如果你要返回结果集明确指定需要的字段别懒。函数这块MySQL 8.0 的内置函数基本够用。比如遇到“mysql datepart”其实 MySQL 里没有DATEPART对应的是EXTRACT(YEAR FROM date)或者DATE_FORMAT()。这里提醒一下别把 SQL Server 的习惯直接搬过来。真碰到不熟悉的函数先去查官方文档别凭直觉写。4. 高并发与性能调优的底层逻辑4.1 连接池与参数调优很多项目一遇到用户量上涨第一反应是“加服务器”其实很多时候是数据库连接池和参数没调好。“mysql高并发解决方案”这个热搜词底下能看到一堆人疯狂加max_connections。但我想说的第一个值是max_connections真不是越大越好每个连接都要占线程和内存默认 151 在大部分 Web 应用里都够。更值得调的是innodb_buffer_pool_size。这个参数决定了 InnoDB 在内存里缓存索引和数据页的容量。如果比比如你说你的机器是 16G 内存innodb_buffer_pool_size建议设置成 8G 到 12G但别全给 MySQL留给操作系统一点余地。调大这个值之后你会发现磁盘读大量减少性能提升立竿见影。我踩过的一个坑是调大 buffer pool 之后忘了检查容器的内存限制结果 OOM进程直接被系统杀掉。所以如果是 Docker 部署记得同步改容器的--memory参数。连接池这块应用层比数据库层更重要。Java 里常用 HikariCP默认最大连接数建议 10~20 就够一个微服务使用了。我见过有人手滑配了 200直接把数据库的连接数打满其他服务全连不上。好的做法是给数据库设置一个最大连接数上限比如 300应用各节点累计的连接数不要超过这个值。另外连接池的前台连接要设置wait_timeout和interactive_timeout较短比如 60 秒避免大量“空闲连接”占着数据库水位不释放。4.2 读写分离与主从复制该怎么做“mysql高并发解决方案”里读写分离是绕不开的经典方案。它的底层是主从复制也就是一台主库负责写多台从库负责读binlog 把主人的数据变更同步过去。MySQL 8.0 在主从同步上已经比较稳搭建也很直接。简单记录一下核心步骤先在主库的配置文件里开server-id1和log-binmysql-bin然后创建一个复制专用账号并授予REPLICATION SLAVE权限。接着在从库配置文件中设置server-id2不能和主库一样启动从库后执行CHANGE MASTER TO MASTER_HOST主库IP, MASTER_USERrepl, MASTER_PASSWORD密码, MASTER_LOG_FILEmysql-bin.000001, MASTER_LOG_POS154; START SLAVE;这个过程不难难的是日常维护。我经常看到从库延迟几千秒查半天发现是某个大事务在主库执行了 10 秒同步在从库被某个大查询卡住了。解决方案是尽量拆分大事务把一次批处理拆成小批次同时给从库的查询路由加点阈值超过一定延迟就把流量切回主库避免读到脏数据。读写分离之后还有个常见误区是“所有读都走从库”。其实一些关键查询比如订单支付状态最好强一致读主库否则用户刚支付成功刷新页面还看到未支付体验很差。我的经验是把实时性要求严格的查询强制走主库允许几秒延迟的统计报表才走从库。5. 常见问题排查技巧实录5.1 安装启动失败的经典场景热搜里“mysql 在运行找不到服务”“安装mysql启动服务报错”“mysql installer for windows”这些词说明很多人在装 MySQL 时卡了几步。这里把经典问题列成一张速查表方便快速定位现象原因解决net start mysql提示服务名无效服务实际名字不是 mysql可能叫 MySQL8用 sc query服务启动后又自动停止数据目录没有初始化执行mysqld --initialize-insecure初始化后my.ini不生效文件编码不对或路径不对检查基于目录路径另存为 ANSI连接时提示Access deniedroot 密码错误用mysqld --skip-grant-tables重置密码端口 3306 被占用可能存在其他 MySQL 或端口冲突netstat -ano找 PID改成 3307我还有一个经验新手千万别同时装mysql-installer和压缩包版两者会互相干扰服务列表。最好只保留一个版本把另一个卸载干净再到C:\Program Files\MySQL和C:\ProgramData\MySQL检查残留文件。5.2 SQL执行报错与数据安全“mysql写入格的数据超过会怎样”这个问题对应的是 MySQL 的严格模式。在 MySQL 5.7 之前如果你插入的字符串超过了字段长度数据库往往会静默截断数据就那么丢了。5.7 之后默认开启严格模式直接报错Data too long for column。这对开发是好事能逼你提前发现数据问题。但也会让一些老业务突然跑不起来。解决方案是调整字段长度或者把 INSERT 语句的列类型改成 TEXT别试图关掉严格模式否则数据完整性迟早出问题。另一个常见报错是 SSL 连接错误热搜里也有“mysql ssl连接错误”。8.0 默认开启了 SSL但某些老客户端版本不支持会出现SSL connection error。解决方法有两个要么升级客户端驱动要么在 JDBC 连接串里加上useSSLfalseallowPublicKeyRetrievaltrue。我建议前者毕竟不记录连接安全的项目迟早要补课。数据安全方面我还要强调一个习惯任何线上库在执行UPDATE/DELETE之前先SELECT同条件看一遍结果集确认数量和影响面。别觉得多此一举我亲眼见过一条 WHERE 条件漏写导致全表被 update 成同一值的惨案。如果你是在命令行操作还可以加一句LIMIT 10先试运行确认无误后再去掉。一些实践后的体感折腾了这么多年 MySQL我真的越来越不Care 它是不是老二。数据库这个领域用的久、维稳、不出事故比什么都重要。MySQL 就像一把老式机械键盘没有那么多花哨的光效但敲下去手感扎实配件遍地都是坏了也容易修。如果你手头的新项目正在“MySQL 还是 PostgreSQL”之间纠结我的建议是看团队熟悉度看现有中间件生态看运维能力。如果这些都是 MySQL 的老熟人果断继续用如果想尝鲜、特性要求高PostgreSQL 也值得游泳一圈。但别让“老二”这个帽子晃了你的手工具是你手里的剑熟练的剑才是好剑。
返回列表