ARTICLE DETAIL

资讯详情

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

MySQL从入门到实战:表设计、索引优化与主从复制避坑指南

MySQL从入门到实战:表设计、索引优化与主从复制避坑指南 1. 环境准备先把 MySQL 跑起来再谈其他很多想学 MySQL 的朋友第一关就卡在安装上。网上搜出来的教程五花八门有的让你去官网下载有的推荐用 Homebrew还有的让你装集成环境。这里我不讨论哪种方式绝对正确只说我在实际部署和教学过程中验证过的、最不容易出问题的路径。1.1 版本选择比你想的更关键先说结论新项目直接上 MySQL 8.0老项目继续用 5.7 不要手痒升级。8.0 相比 5.7 在排序、索引、字符集支持上都有明显改进默认字符集从 latin1 改成了 utf8mb4这对中文内容尤其友好。你想想2010 年之前建的站还在用 latin1存中文全靠运气后来升级到 utf8 又发现 emoji 存不进去最后全行业都乖乖切到 utf8mb4——这件事我建议你别走弯路一步到位。8.0 的坑也有比如默认认证插件是 caching_sha2_password老版本的客户端和很多 PHP 版本连不上。我遇到过不止一次程序代码迁移到新服务器MySQL 是 8.0结果应用报 authentication 错误排查半天发现是驱动太旧。解决办法是改回 mysql_native_password或者干脆升级驱动——我的建议是升级驱动因为改认证插件是临时方案。安装方式上Windows 用户下载 zip 压缩包解压后初始化就行千万别去点那个 200MB 的安装向导版本那玩意儿会在你机器上装一堆你用不上的组件。Linux 用户用 apt 或 yum 安装官方仓库版本比源码编译省心一万倍。至于 Navicat 破解版之类的我的态度很明确数据库工具是你的吃饭家伙别在这上面省Community 版 DBeaver 完全够用免费且跨平台。1.2 连接不上八成是这三个原因排在第一的坑是 socket 连接问题。你搜“error 2002 (HY000): cant connect to local MySQL server through socket /tmp/mysql.sock”会发现满屏都是解决方案。这个问题的本质是客户端去找默认 socket 文件但实际 socket 不在那个位置。我排查的固定思路是这样先确认服务有没有起来systemctl status mysqld或service mysql status看进程状态。服务正常的话再查 socket 文件位置——mysqladmin variables | grep socket能看到实际路径。如果是自己编译安装或者做了多实例部署socket 位置基本都会偏离默认值。解决方式是连接时显式指定mysql -u root -p -S /var/run/mysqld/mysqld.sock或者干脆改用 TCP 方式连mysql -u root -p -h 127.0.0.1 -P 3306。第二坑是账号权限。我见过最诡异的情况同一个账号在命令行能连在应用里连不上。后来发现问题出在host字段——MySQL 的账号是“用户名 来源主机”二元组rootlocalhost和root%是两个完全不同的账号。应用服务器通过局域网 IP 过来命中的是root%如果这个账号没建或者密码不对认证就挂。解决办法简单粗暴给应用用的账号单独创建host 限定为应用服务器 IPCREATE USER app_user192.168.1.100 IDENTIFIED BY StrongPassword!; GRANT ALL PRIVILEGES ON mydb.* TO app_user192.168.1.100; FLUSH PRIVILEGES;第三个坑是 SSL 连接错误。MySQL 8.0 默认开了 SSL但很多数据库连接池和驱动没有正确配置证书就会报SSL connection error。如果你在内网环境追求的是性能而非传输加密可以在连接串里直接禁用 SSL。JDBC 的话就是加?useSSLfalseallowPublicKeyRetrievaltrue这个组合我几乎天天用。2. 从建库到增删改查SQL 不是背出来的安装好环境后很多人捧着《SQL 必知必会》从头翻到尾合上书却写不出一条像样的查询。我的建议是直接拿一个真实需求来练手。博客系统就是最好的练习项目——字段类型够丰富关联关系够典型规模不至于复杂到劝退新手。2.1 用户表设计里藏着的门道以“第 1 关数据库表设计——用户信息表”为例。很多课程设计作业里学生交上来的用户表长这样id、username、password、email、phone五六个字段完事。这种表在作业里能拿分在真实项目里会被运维骂死。你至少要补上这些字段CREATE TABLE user ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password_hash VARCHAR(255) NOT NULL, email VARCHAR(100) NOT NULL, phone VARCHAR(20) DEFAULT NULL, status TINYINT NOT NULL DEFAULT 1, email_verified TINYINT NOT NULL DEFAULT 0, avatar_url VARCHAR(255) DEFAULT NULL, last_login_at DATETIME DEFAULT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username), UNIQUE KEY uk_email (email) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;这里面有几个点新手特别容易搞不明白。第一为什么密码字段要 255 字节而不是直接VARCHAR(32)因为你根本不应该存明文密码甚至不应该用 MD5 单向散列——那个早就被彩虹表打穿了。现代做法是用 bcrypt 或 Argon2这类算法的输出长度远大于 32 字节。所以密码字段留大一点是为了配合安全的哈希算法。第二为什么要有status字段因为删除有物理删除和逻辑删除两种。用户说“我要注销账号”你咔一删他关联的文章、评论、订单全部变成孤儿数据外键约束直接炸没有外键约束就产生脏数据。所以线上系统几乎都保留status字段用户“删除”后变成禁用态数据还在但不可登录——这个设计模式叫软删除。第三BIGINT而不是INT为什么现在看着数据量小但表一旦上百万行INT 的上限 21 亿看着够用可你要知道自增主键的分配策略、分库分表的场景会放大这个风险。用 BIGINT 多一点存储空间但换来的是未来十年的从容。创建表之后务必马上验证和调整。验证方式有几个用SHOW CREATE TABLE user;检查 DDL 是否符合预期用DESC user;查看字段结构然后插入一条测试数据检查是否有报错。INSERT INTO user (username, password_hash, email) VALUES (test_user, $2y$10$abcdef..., testexample.com);2.2 CRUD 的正确姿势不只是 INSERT 和 SELECT增删改查是数据库的日常操作但是很多人在写 UPDATE 和 DELETE 的时候忘了带 WHERE 条件。你在本地练习时无伤大雅在线上生产环境一条UPDATE user SET status1没有 WHERE恭喜你全表用户状态被你重置了。MySQL 默认没有开--safe-updates模式这种事故没有任何缓冲只能靠日志恢复——这也是为什么我会建议新手把sql_safe_updates1加到配置文件里强制要求带 WHERE 才能执行 UPDATE/DELETE。SELECT 查询的威力在于灵活组合条件和排序。比如热搜词里反复出现的“mysql排序”基本功其实就是ORDER BY的几种用法-- 按注册时间倒序最新的在前 SELECT id, username, created_at FROM user ORDER BY created_at DESC; -- 多字段排序先按状态再按最后登录时间 SELECT id, username, status, last_login_at FROM user ORDER BY status ASC, last_login_at DESC; -- 带排序的分页查询配合索引效果更佳 SELECT id, username FROM user WHERE status 1 ORDER BY id DESC LIMIT 10 OFFSET 20;LIMIT分页很简单但有个陷阱OFFSET越大查询越慢因为数据库要扫描并丢弃前面所有的行。当你的博客系统做到第 1000 页的时候OFFSET 9990会很酸爽。我实操中的改进做法是“游标分页”用上次拿到的最后一条记录的 id 作为边界SELECT id, username FROM user WHERE status 1 AND id 上次最后一条的id ORDER BY id DESC LIMIT 10;这种方式无论翻到多深的页码查询速度都恒定。数据量过百万后你会回来感谢这个方案的。聚合查询同样是高频需求。统计用户数量、分组统计文章数、计算平均值——这些会用到COUNT、GROUP BY、HAVING。比如统计博客系统里每个分类下的文章数SELECT c.name, COUNT(a.id) AS article_count FROM category c LEFT JOIN article a ON a.category_id c.id GROUP BY c.id, c.name HAVING COUNT(a.id) 0 ORDER BY article_count DESC;注意这里用LEFT JOIN保证没有文章的分类也能显示出来用HAVING过滤分组后的结果而不是用WHERE——WHERE在分组之前被过滤HAVING是针对分组结果的过滤这个区别是面试题常客也是新手写错的高发点。2.3 存储过程能不用就不用但必须会用热搜词里出现了“mysql存储过程”我得说两句实在话。存储过程的优势是封装复杂逻辑、减少网络往返但劣势也很明显调试困难、版本管理困难、数据库耦合度高。我的原则是业务逻辑尽量在应用层写存储过程只在两种场景下考虑——一是极其复杂的报表统计二是多个事务步骤需要数据库端确保原子性。真要用语法也不复杂。我写过最长的一个存储过程是商城的订单超时自动关闭逻辑大概一百多行里面用了游标、循环、异常处理DELIMITER $$ CREATE PROCEDURE close_timeout_orders() BEGIN DECLARE done INT DEFAULT 0; DECLARE v_order_id BIGINT; -- 查询超时订单的游标 DECLARE order_cursor CURSOR FOR SELECT id FROM orders WHERE status PENDING AND created_at NOW() - INTERVAL 30 MINUTE; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done 1; OPEN order_cursor; read_loop: LOOP FETCH order_cursor INTO v_order_id; IF done THEN LEAVE read_loop; END IF; -- 将订单标记为关闭记录关闭原因 UPDATE orders SET status CLOSED, close_reason TIMEOUT WHERE id v_order_id; END LOOP; CLOSE order_cursor; END$$ DELIMITER ;写完后用CALL close_timeout_orders();调用即可。要注意的是DELIMITER的切换MySQL 默认以分号作为语句分隔符而存储过程体内部有大量分号所以必须先用DELIMITER $$改成其他符号否则还没定义完就被截断了。这个细节困扰过我一整晚。实际项目中我更推荐的替代方案是用定时任务Linux crontab 或应用框架的调度器去调用一个应用层的脚本逻辑清晰、可测试、可 debug。存储过程适合的场景大多是历史遗留系统维护新项目建议面向未来设计少引入这层复杂度。3. 数据库设计别让表结构成为项目的地基裂缝数据库设计是项目的底层架构。我见过太多项目死在重构数据库的路上。一开始图省事字段全用 VARCHAR该拆的表不拆等数据量上来之后想改代价堪比拆楼重盖。从入门阶段就建立正确的设计意识比事后补救省太多钱。3.1 需求分析的输出E-R 图和它的现实意义设计数据库的第一步不是建表而是搞清楚系统有哪些实体、实体之间什么关系。E-R 图实体-联系图就是这个阶段的核心产出物。以博客系统为例。实体至少有用户、文章、分类、评论、标签。关系梳理如下一个用户可发布多篇文章一篇文章属于一个用户1:N一个分类下有多篇文章一篇文章属于一个分类1:N一篇文章有多条评论一条评论属于一篇文章1:N一篇文章可以有多个标签一个标签可对应多篇文章M:NM:N 关系不能直接建两个表搞定必须引入中间表文章表 标签表 文章标签关联表。这是新手设计时最容易犯的错误——直接在文章表里加一个tags字段存“技术,生活,随笔”后续查询某个标签下所有文章时你会被 LIKE ‘%生活%’ 这种写法折磨到怀疑人生。索引完全失效查询速度慢得像蜗牛。正确做法CREATE TABLE tag ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_tag_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE article_tag ( article_id BIGINT UNSIGNED NOT NULL, tag_id BIGINT UNSIGNED NOT NULL, PRIMARY KEY (article_id, tag_id), KEY idx_tag_id (tag_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;中间表的联合主键天然保证了同一文章不会重复打同一个标签。查询“生活”标签下的文章时走的是article_tag表的索引飞快的。3.2 三范式理解“为什么这么拆”比背定义更重要数据库设计必谈三范式教科书上绕不开面试也爱问。我用自己的话给你翻译一遍第一范式字段不可再分。意思是一个字段只存一个值别把“北京市海淀区”拆成“北京、市、海淀、区”存四个字段除非你有按区统计需求也别把多个电话号码塞进一个字段里。第二范式非主键字段必须完全依赖主键而不是依赖主键的一部分。仅对联合主键有意义比如订单明细表用订单ID商品ID作为联合主键那么“商品名称”只依赖商品ID不依赖订单ID这就违反了第二范式应该拆出去。第三范式非主键字段之间不能有传递依赖。“用户所在城市的名称”不能存在订单表里——城市名称依赖城市ID城市ID依赖用户ID用户ID才是订单表的合法外键。城市名称应该存在用户表或城市表里。但在真实项目中我经常“故意”违反第三范式。举个最典型的例子订单表里冗余一个“用户名”。按三范式应该通过 user_id 去 JOIN 用户表获取用户名但在电商后台订单查询是最高频的操作每次都要 JOIN 用户表会白白消耗性能。这时候把username冗余进订单表以微量的存储空间换取查询效率的大幅提升——这就是反范式设计。所以我的实际建议是设计时先按三范式来保证逻辑干净然后在性能瓶颈处有针对性地引入冗余字段。这就像盖房子先按规范打地基装修时可以按生活习惯微调承重墙不能动但隔断墙可以按需改造。你要分得清哪些是“承重墙”核心表、核心字段哪些是“隔断墙”冗余字段、辅助索引。3.3 字段类型的三条铁律和一个误区关于字段类型选择我在实际项目中总结了三条铁律铁律一整数用 INT/BIGINT不要用 VARCHAR 存数字。手机号这种看似数字的字段因为可能涉及区号、前导零用 VARCHAR(20) 特殊处理。真正的数值型字段加减乘除和比较排序整数类型比字符串快一个量级。铁律二小数不要用 FLOAT/DOUBLE用 DECIMAL。FLoat 是浮点存储精度会丢钱算错了没人赔你。DECIMAL(10,2) 的意思是最多 8 位整数加 2 位小数范围刚好覆盖绝大多数交易金额。铁律三时间用 DATETIME 或 TIMESTAMP别用字符串。我不知道为什么还有人在用 VARCHAR 存时间可能是为了方便查看但后果是不能直接按时间排序那将按字典序排、不能用时间函数做日期运算、索引效率低。DATETIME 和 TIMESTAMP 的差别在于存储空间和时区感知MySQL 8.0 之后我统一用 DATETIME配合应用层统一按 UTC 存储、按本地时区展示逻辑上最不容易出岔子。要说误区最典型的是把INT(11)当成是一种限制——INT(11)里的括号数字只控制显示宽度不限制存储范围存 21 亿照样可以括号里的 11 几乎没意义。很多老博客把这个当知识点讲其实是误解。显示宽度在 MySQL 8.0 里已经废弃了别再被带偏。3.4 主键方案自增 vs 雪花 vs UUID主键怎么选是个老生常谈但真能吵起来的话题。我的结论单机或简单主从架构自增主键就是最好的选择分布式场景雪花 ID 起步UUID 字符串做数据库主键默认不推荐。自增主键的好处是有序递增InnoDB 的聚簇索引按主键顺序物理存储插入效率极高范围查询也快。担心爬虫可以通过 id 遍历数据那应该用权限和访问控制解决而不是换主键策略。雪花 ID 是分布式场景下保证全局唯一且大致有序的方案核心思路是用时间戳 机器标识 序列号拼出一个 64 位长整型。很多语言的框架都有现成实现比如 MyBatis-Plus 的 ASSIGN_ID你不需要自己实现生成算法但需要知道在分库分表时把生成好的 ID 传给数据库而不是依赖数据库自增——因为每个库各自自增必然撞车。UUID 最大的问题是字符串类型存储占用空间大且完全无序插入时 InnoDB 的 B 树需要频繁页分裂性能会断崖式下降。如果第三方系统硬要用 UUID 作为关联键你内部可以保留自增主键UUID 仅作业务标识列加唯一索引即可。4. 规范的威力从命名习惯到文档沉淀这个章节也是应对热搜词“规范化”这个概念的重点。数据库设计的规范化不只指范式层面的“规范化”还包括工程层面的“规范习惯”。4.1 命名规范一张表告诉你可以怎么统一我发现团队里最大的协作成本不是谁的技术水平低而是每个人的命名风格都不一样。同一个字段A 写userNameB 写user_nameC 写username等到联调的时候全是泪。所以命名规范必须固定下来落到文档里对象规范示例数据库名小写 下划线blog_db表名小写复数或单数但全队统一users字段名小写 下划线语义明确created_at主键索引PRIMARYid 字段自动创建唯一索引uk_前缀uk_username普通索引idx_前缀idx_article_category外键约束fk_前缀fk_comment_article表名用单数还是复数业界没有统一但你别混着来。我推崇单数因为SELECT * FROM user比SELECT * FROM users读起来更符合“查一张表的结构”的直觉。哪种无所谓统一就好。字段命名的忌讳也多简单说三个高频的别用name做字段名太泛看不出是谁的名字别用 MySQL 保留字做表名和字段名比如order、group、desc真要用来不及改就加反引号包起来但下次请提前换掉别用拼音缩写yhm、sjc这种连作者自己过两周都看不懂何况接手的人。4.2 索引设计不是越多越好每个索引都要有理由索引是 MySQL 性能的核心但在新手项目中常见两个极端要么完全不用索引全表扫描扛到底要么一把梭给所有字段都加索引写入变慢存储膨胀查询也没变快。我的索引设计心法三句话第一索引服务的是查询模式不是字段你得先分析业务到底有哪些查询条件、排序、关联第二联合索引有最左前缀原则(a, b, c)索引能命中a,ab,abc但不能命中b或c所以字段顺序要把区分度高的放前面第三覆盖索引是性能利器——如果一个查询的 SELECT 字段都在索引里就不需要回表速度提升离谱。建索引实操建议-- 高频查询按分类查文章建普通索引 CREATE INDEX idx_article_category ON article(category_id); -- 高频查询按用户查其发布的文章时间倒序 CREATE INDEX idx_article_user_time ON article(user_id, created_at DESC); -- 高频登录按用户名查用户已有唯一索引则无需额外建 -- 三范式拆表后的外键字段通常都需要索引 ALTER TABLE comment ADD INDEX idx_comment_article (article_id);判断索引有没有生效用EXPLAINEXPLAIN SELECT * FROM article WHERE category_id 3 ORDER BY created_at DESC;看type列和key列type 到ref或range就说明走了索引type 是ALL就是全表扫描需要检查索引是否正确命中。很多新手想不到的一个常识是表数据量小的时候全表扫描不一定比索引慢因为数据库优化器会评估代价选择最优路径。所以你建了索引但EXPLAIN显示没走索引如果是小表那可能是合理的别慌。当表超过几万行后索引的优势会越来越明显。4.3 文档和缺陷管理习惯程序员最讨厌但最该做的事热搜词提到了“养成规范化文档与缺陷管理习惯”这听起来像软技能但在数据库项目里它是实打实的硬需求。我吃过最大的亏是接手一个五年历史的系统表有上百张没有一张表结构文档没有字段注释没有 ER 图。每次排查问题都要SHOW CREATE TABLE然后肉眼猜字段含义效率低到令人崩溃。所以我现在对自己和团队的要求很简单三条第一建表时必须写字段注释。在 DDL 里加 COMMENT 几乎零成本但收益巨大CREATE TABLE product ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, name VARCHAR(200) NOT NULL COMMENT 商品名称, price DECIMAL(10,2) NOT NULL COMMENT 售价元含税, stock INT NOT NULL DEFAULT 0 COMMENT 库存数量, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态0下架1上架, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT商品表;第二每次结构变更都要留记录。我现在习惯把建表和变更脚本按版本号放目录里比如V20240101__create_product_table.sql配合 Flyway 或 Liquibase 之类的工具自动执行。这样任何环境都能从零复现出最新结构而不是靠人工在测试库上“手搓”。第三缺陷记录不要只在脑子里记。我会在项目文档库维护一个数据库问题速查表按问题现象、可能原因、解决方案、涉及版本四列记录。比如“MySQL 排序结果不对”可能原因写“字符集排序规则不一致utf8_general_ci 和 utf8_unicode_ci 对中文排序不同”方案写“统一使用 utf8mb4_unicode_ci”。这个速查表随着项目演进越来越值钱新同学接手后遇到问题直接查表不用再踩一遍前辈踩过的坑。5. 进阶操作与高频疑难排查主从同步与性能优化当你完成基础阶段的任务会逐渐接触生产环境的一些问题数据备份、读写分离、同步延迟、大数据量查询慢等。搜索热词里“主从复制”“同步工具”的出现说明这是很多人到了某个阶段就会遇到的需求。我把最常见的几个实操场景梳理一下。5.1 主从复制配置不难难在理解它的定位主从复制这个词听起来像高深技术拆开说就是主库负责写入从库负责读主库的 binlog二进制日志传给从库从库把日志重新执行一遍数据就同步过去了。它的核心用途有三个读写分离提升吞吐、容灾切换、数据分析不干扰主库业务。配置步骤其实就那么几步。先在主库的配置文件中开启 binlog 并设置 server-id[mysqld] server-id 1 log_bin /var/log/mysql/mysql-bin.log binlog_format ROW expire_logs_days 7然后在主库创建用于复制的专用账号CREATE USER replica_user% IDENTIFIED BY ReplicaPass123!; GRANT REPLICATION SLAVE ON *.* TO replica_user%; FLUSH PRIVILEGES;查看主库当前 binlog 位置SHOW MASTER STATUS;记下 File 和 Position 两列的返回值这是从库开始同步的起点。接着配置从库的 server-id必须和主库不同并执行CHANGE MASTER TO MASTER_HOST主库IP, MASTER_USERreplica_user, MASTER_PASSWORDReplicaPass123!, MASTER_LOG_FILEmysql-bin.000001, MASTER_LOG_POS154; START SLAVE; SHOW SLAVE STATUS\G关注Slave_IO_Running: Yes和Slave_SQL_Running: Yes两个都是 Yes 就表示同步链路正常。任何一个变 No就看Last_IO_Error或Last_SQL_Error字段它们会直接告诉你错在哪。我实操中的几条经验一是主从之间网络延迟大时把从库参数slave_net_timeout调大一点默认 60 秒防止网络抖动导致 IO 线程频繁断连二是主从数据不一致时如果是少量数据不一致用pt-table-checksum检查pt-table-sync修复这俩工具比我手动写 SQL 靠谱太多三是 GTID 模式全局事务标识符相比传统 binlog 位置方式切换和故障恢复更省心MySQL 8.0 默认支持建议直接用 GTID 方式配置。5.2 单表数据量大分区、分表和归档哪个适合你单表数据量过千万后即使有索引查询可能也会变慢。别急着上分库分表这种重型武器先评估三个更轻量级的方案。分区表是 MySQL 内置功能逻辑上是一张表物理上按规则分成多个区。比如按时间范围分区CREATE TABLE logs ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, log_time DATETIME NOT NULL, content TEXT, PRIMARY KEY (id, log_time) ) ENGINEInnoDB PARTITION BY RANGE (YEAR(log_time)) ( PARTITION p2022 VALUES LESS THAN (2023), PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION p_future VALUES LESS THAN MAXVALUE );分区的好处是查询时如果条件命中了分区键数据库只扫描对应分区速度提升明显删除旧数据直接ALTER TABLE logs DROP PARTITION p2022;比DELETE FROM logs WHERE log_time 2023-01-01快几个数量级。分区表的限制也不少分区键必须包含在所有唯一索引和主键里这个约束很磨人。所以我通常只在日志表这类数据上用分区业务核心表不用。分表是把一张大表物理拆成多张结构相同的小表比如按用户 ID 取模拆成 16 张表路由逻辑写在应用层或者中间件层。这是应对数据规模增长的系统方案需要投入大量改造工作量。我的建议是除非确定业务会涨到亿级数据否则别主动上分表。拆表一时爽联表火葬场跨表查询和分页都会变成噩梦。归档则是最务实的方案——绝大部分业务表的热数据只占一小部分把一年前的数据挪到归档表或者历史库里主表数据量骤降查询速度自然恢复。我自己处理过一个 3000 万行的订单表归档了 80% 的历史订单后主表只剩 600 万行日常查询从两秒多降到几十毫秒期间没有改一行业务代码。5.3 工具链推荐数据库管理和同步的实用选择管理工具方面我前面提到了 DBeaver但不同场景有更顺手的工具。日常开发和调试用 Navicat 的人确实多界面好看、导入导出方便不是不能用只是注意别用破解版安全和法律风险都不划算。DBeaver 开源版够用另外 JetBrains 家的 DataGrip 对 SQL 编辑和代码提示体验很好写复杂查询的体验不错。数据库同步工具有几个场景要区分清楚日志型实时同步用 MySQL 自带主从复制完全够异构数据源或者表级别精准同步可以考虑 Canal阿里巴巴开源的 binlog 订阅组件它把 MySQL 的变更事件转成消息下游可以对接 Elasticsearch、数据仓库等。etl 批量同步比如 Excel 导入数据库用 Navicat 的导入向导已经足够数据量大到一定规模考虑 DataX 或 Kettle。我在一个项目里用 DataX 做离线全量同步5 分钟跑了 200 万行数据体验相当稳定。Excel 导入数据库单说这个实操。先把 Excel 另存为 CSV 文件注意编码选 UTF-8分隔符选逗号然后到 MySQL 里LOAD DATA LOCAL INFILE /path/to/your/file.csv INTO TABLE temp_import FIELDS TERMINATED BY , ENCLOSED BY LINES TERMINATED BY \n IGNORE 1 ROWS;IGNORE 1 ROWS是跳过表头。导入前建议先存入临时表做一轮数据清洗校验再 INSERT INTO 正式表。直接导入正式表一旦源文件数据有问题后续清理非常痛苦。5.4 一张速查表解决 90% 的日常疑难最后把我这些年遇到的典型问题和处理方式整理成一张速查表按我的经验这些问题覆盖了日常运维和开发工作中九成以上的坑问题现象常见原因快速排查/解决2002 连接 socket 失败socket 路径不一致或服务未启动查 mysqld 是否运行连接时指定-Ssocket 路径或用-h 127.0.0.11045 访问被拒绝账号 host 不匹配或密码错误用 root 登录SELECT user, host FROM mysql.user;核对账号1146 表不存在用错库名或大小写不一致确认USE的库lower_case_table_names参数统一大小写策略1205 锁等待超时事务未提交导致行锁未释放SHOW PROCESSLIST;查到状态为Locked的事务KILL 对应 ID1452 外键约束失败插入数据引用了不存在的外键值检查父表数据确认外键字段匹配Sql 查询慢缺少索引 / 大量 JOIN / 数据量大EXPLAIN看执行计划扫描行数大的加索引考虑归档或分区SSL 连接错误驱动版本和 MySQL 8 认证/SSL 不兼容升级驱动或 JDBC 加useSSLfalseallowPublicKeyRetrievaltrue排序结果不对字符集排序规则不一致统一 COLLATE 为utf8mb4_unicode_ci主从同步 SQL 线程停止主从数据不一致或重复主键SHOW SLAVE STATUS;看 Last_SQL_Error用 pt-table-sync 修复或手动补数据导入大数据文件卡死max_allowed_packet 过小调大max_allowed_packet如SET GLOBAL max_allowed_packet256M;数据库的学习曲线和很多技术不一样它不是一种“看会了就会了”的知识而是“你亲手踩过坑下一次才知道怎么绕”。我自己也是从建了一张没有主键的用户表、然后被数据查得想哭开始一步步走到今天的。你现在看到的所有配置文件、规范文档、速查表都是我用“血泪”换出来的。如果这篇文章只能留一句话给你那就是表结构设计宁可慢一点、多花一天去推敲也不要图快建完之后悔三个月。MySQL 本身只是一个工具真正拉开差距的是你脑中的设计思路和手上的排查习惯。把这个地基打牢后续不管做什么系统都会顺畅很多。
返回列表