写这个系列写到第10篇,刚好是时候把mysql从安装到实战的完整链路认真梳理一遍了。如果你正在学mysql,或者已经开始在windows上折腾安装、用workbench和navicat连库、写update和存储过程、被锁表卡到怀疑人生——那这篇就是给你准备的。
mysql这东西,入门不难,难的是从“能跑”到“跑得稳”。前9篇笔记里我拆过安装、基础语法、事务这些具体话题,这一篇干脆把散落的经验集中成一份实战闭环:从版本选择开始,到windows下的安装配置,到连接报错排查,再到建表改表、高频语法、存储过程触发器,最后落到面试题和容器化部署。属于那种可以收藏起来、遇到问题直接翻的实操合集。
1. 版本选择与Windows安装的取舍逻辑
1.1 先弄清5.7和8.0的区别,再决定装哪个
很多人在mysql官网下载页面前纠结半天。我直接说结论:如果你是全新项目、没有历史包袱,装8.0;如果你是要接手老项目、用的框架版本比较旧,装5.7。
8.0相比5.7的几个明显变化,直接决定你的项目能不能兼容:
- 默认认证插件从
mysql_native_password换成了caching_sha2_password,客户端版本太旧会连不上,后面我会专门讲这个坑。 - 新增了窗口函数、CTE(公共表表达式),写复杂统计SQL时舒服很多。
- 支持了
CHECK约束的强制执行,5.7里这个约束是摆设。 utf8mb4成为默认字符集,emoji存储不再有编码问题。
但8.0的代价是,部分老版本的jdbc驱动、navicat、sqoop在连接时会出现兼容性问题。5.7生态成熟,网上能搜到的资料也最多,很多生产环境至今还在跑5.7,学它不亏。
1.2 解压安装与初始化配置的完整步骤
在windows上装mysql,我推荐用免安装的zip包,而不是msi图形安装器。原因很简单:zip包解压即用,环境变量一配就完事,出了问题也容易重来;msi安装器虽然看着省事,但它会在系统里注册成服务,卸载不干净时反而各种残留。
具体步骤是:
- 到mysql官网下载对应版本的zip包,比如
mysql-8.0.xx-winx64.zip。 - 解压到指定目录,比如
D:\mysql-8.0.xx,注意路径里尽量不要有中文和空格。 - 在解压目录下新建
my.ini配置文件,这是mysql服务的核心配置文件。
一份最基础可用的my.ini如下:
ini复制[mysqld]
basedir=D:/mysql-8.0.xx
datadir=D:/mysql-8.0.xx/data
port=3306
character-set-server=utf8mb4
default-storage-engine=INNODB
[client]
default-character-set=utf8mb4
- 以管理员身份打开cmd,进入mysql解压目录的bin文件夹,执行初始化命令:
bash复制mysqld --initialize-insecure
--initialize-insecure表示初始化一个root密码为空的实例。这个细节很多人栽过:如果只用--initialize,会生成一个随机密码,写在data目录下的err日志里,找半天找不着。用--initialize-insecure则直接空密码,方便第一次登录。
- 启动服务:
bash复制mysqld --install
net start mysql
mysqld --install是把mysql注册成windows服务,以后可以用net start mysql和net stop mysql控制启停。
- 登录并设置密码:
bash复制mysql -u root -p
ALTER USER 'root'@'localhost' IDENTIFIED BY '你的密码';
第6步的ALTER USER在8.0里是标准改密方式,5.7也可以用SET PASSWORD FOR 'root'@'localhost' = PASSWORD('你的密码');。
安装完成后,把D:\mysql-8.0.xx\bin加到系统环境变量PATH里,以后在任意目录都能敲mysql命令,不用每次cd进bin目录。这个步骤很容易漏,漏了也不影响mysql运行,但会让你后续操作非常烦躁。
1.3 workbench和命令行,别只会一个
很多新手装了mysql以后只会在workbench里点鼠标,这是很危险的。workbench适合查看结果、做设计,真正到服务器上排查问题,Linux环境基本只有命令行可用,所以从第一天开始就要有“命令行优先”的意识。
workbench里的几个高频操作,对应的命令行其实就那几条:
| 操作 | workbench操作方式 | 命令行等价方式 |
|---|---|---|
| 查看所有数据库 | 左侧导航栏点击 | SHOW DATABASES; |
| 新建数据库 | 右键、Create Schema | CREATE DATABASE 库名 DEFAULT CHARSET utf8mb4; |
| 导入SQL文件 | Server菜单、Data Import | mysql -u root -p 库名 < 文件.sql |
| 查看表结构 | 右键表、Table Inspector | DESC 表名; |
在workbench里想快速执行命令,可以直接打开一个新的SQL标签页,或者用File -> New Query Tab, 它会默认连接当前登录的实例。网上有人问“workbench如何快速用命令行新建数据表”,其实就是在SQL编辑区输入CREATE TABLE语句再执行,就这么简单。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 连接报错的完整排查链路:2059只是第一关
2.1 2059错误的本质:认证插件不匹配
如果你用的是mysql 8.0,客户端工具比较旧,比如老版本的navicat、sqoop、firedac,大概率会碰到这个报错:
code复制ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded
这个报错的本质是认证协议不匹配。mysql 8.0默认使用caching_sha2_password插件做密码认证,而老客户端只实现了mysql_native_password协议,双方握手失败。
解决方案有两个方向:
方向一,改mysql端用户的认证插件,让它兼容老客户端。这也是网上最多的做法:
sql复制ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';
FLUSH PRIVILEGES;
改完之后再连接就通了。这个方案治标不治本,因为新插件本身更安全,为了兼容老客户端而降低安全级别,长期看并不可取。
方向二,升级客户端,或者更换支持新认证插件的工具。比如navicat升级到16以上,或者干脆用mysql官方自带的workbench。这个方案更符合安全趋势,也是我推荐的方向。
2.2 navicat、firedac、sqoop,连接问题各有各的坑
navicat连接报2059,按上面方法改mysql_native_password就好。但navicat还有一个隐藏问题:如果你连接的是远程服务器的mysql,必须确认root账号允许远程登录。mysql默认的root用户host是localhost,只允许本机连接。需要创建一个远程账号:
sql复制CREATE USER 'admin'@'%' IDENTIFIED BY '密码';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%';
FLUSH PRIVILEGES;
'%'表示允许任意host连接。这里要强调:生产环境不要用root开远程,用一个授权过的独立账号,遵循最小权限原则。
firedac相关的报错原文是:
code复制[FireDAC][Phys][MySQL] Client does not support authentication protocol requested by server.
这个和2059是一个问题,都是客户端不支持caching_sha2_password。用delphi/c++builder写的老程序特别容易碰到。解决思路一样:要么把mysql用户改成mysql_native_password,要么换用最新版的firedac驱动。
sqoop连接不上mysql则通常是另一个维度的问题:sqoop的lib目录里缺mysql的jdbc驱动包。需要把mysql-connector-java-x.x.x.jar放到$SQOOP_HOME/lib目录下,并且确认驱动版本和mysql版本兼容。有些老教程让你用“连接mysql 8.0需要使用特定版本的connector”,这是对的,要下官方对应版本。
2.3 排查顺序建议
连接失败时别慌,按这个顺序排查最快:
- ping一下服务器ip,确认网络通不通。
telnet 服务器ip 3306,确认mysql端口放通了没有。很多人明明mysql正常,就是防火墙挡了端口,导致外部连不上。- 用命令行本地登录
mysql -u root -p -h 127.0.0.1,确认mysql本身没问题。 - 检查root用户是否有远程权限。
- 检查认证插件是否需要兼容处理。
我处理过的绝大多数“连不上”问题,最后都落在第2步和第4步。端口不通、权限没有,这两件事占了八成。
3. 从建表到改表:字段设计、保留字与锁表风险
3.1 学生课程成绩库的设计范例
热搜里有一条“学生课程成绩信息实体表设计mysql”,这是每个学数据库的人都会遇到的经典场景。看似简单,但这里其实藏着关系型数据库设计的核心思想:怎么拆表、怎么处理多对多关系。
学生和课程是什么关系?一个学生选多门课,一门课可以被多个学生选,这是典型的多对多关系。多对多不能直接在两张表里加外键,必须通过中间表来关联。所以设计是三张表:
学生表:
sql复制CREATE TABLE student (
student_id INT PRIMARY KEY AUTO_INCREMENT,
student_no VARCHAR(20) UNIQUE NOT NULL COMMENT '学号',
student_name VARCHAR(50) NOT NULL COMMENT '姓名',
gender TINYINT DEFAULT 0 COMMENT '0未知 1男 2女',
birth_date DATE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
课程表:
sql复制CREATE TABLE course (
course_id INT PRIMARY KEY AUTO_INCREMENT,
course_name VARCHAR(100) NOT NULL COMMENT '课程名称',
credit DECIMAL(3,1) NOT NULL COMMENT '学分',
teacher_name VARCHAR(50)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
成绩表(中间表):
sql复制CREATE TABLE score (
student_id INT NOT NULL,
course_id INT NOT NULL,
score DECIMAL(5,2) NOT NULL COMMENT '成绩',
exam_date DATE,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES student(student_id),
FOREIGN KEY (course_id) REFERENCES course(course_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这里一个容易被忽略的设计点是:score表的主键是(student_id, course_id)联合主键,它天然保证了同一个学生对同一门课不会录入重复成绩。这比单独加一个自增id更合理。
3.2 字段撞上关键字怎么办
热搜里有一条“mysql表中字段为关键字”,这是新手最容易踩的坑。比如给表设计了一个字段叫desc,或者order,在SQL里一查就报语法错误,因为这些词是mysql的保留关键字。
处理方式有三种:
- 建表时用反引号包裹:
\order` INT`,之后每次查询引用都要夹反引号,麻烦不说,还容易漏。 - 更推荐的做法:字段名加上业务前缀。
order改成order_no,desc改成description。这样既避免关键字冲突,语义也更清晰。 - 实在改不了,可以用
ALTER TABLE给字段改名。
我的习惯是:所有字段名尽量用有业务含义的单词组合,比如主键叫xxx_id,时间字段叫created_at、updated_at。这不仅是为了避开关键字,更是一种工程规范,后期维护代码时一眼能看懂含义。
3.3 ALTER TABLE改结构时的注意事项
数据库上线以后,改表结构是免不了的。mysql的ALTER TABLE语法很简单:
sql复制ALTER TABLE student ADD COLUMN phone VARCHAR(20) AFTER student_name;
ALTER TABLE student MODIFY COLUMN phone VARCHAR(30);
ALTER TABLE student DROP COLUMN phone;
ALTER TABLE student RENAME COLUMN phone TO phone_num;
但上线环境改表要特别小心,核心问题是锁表。ALTER TABLE在mysql 5.6之前会锁整张表,期间所有读写都阻塞。5.6之后引入了在线DDL,部分操作可以并发DML,但仍然分情况:
ADD COLUMN在innodb下通常是INSTANT或INPLACE,不会长时间锁表,但大表上还是可能有短暂元数据锁。MODIFY COLUMN修改字段类型时,如果涉及到数据转换,可能触发表重建,这是最耗时的操作,几百GB的表可能执行很久。DROP COLUMN也是重建表级别的操作,线上要谨慎。
所以我的建议是:变更表结构尽量放在低峰期,先在小表上测试耗时,再在大表上操作。如果表实在太大,可以考虑用gh-ost、pt-online-schema-change这类在线改表工具,不过那是另一个话题了。
3.4 锁表:为什么你的update卡住了
热搜里有一条“mysql锁表”,几乎是生产环境必踩的坑。场景往往是这样:执行一个UPDATE语句,然后一直卡着不动,最后报Lock wait timeout exceeded。
常见原因有三个:
- 事务没提交。有事务开启了
UPDATE或DELETE,但一直没COMMIT或ROLLBACK,它持有的行锁就一直没有释放,其他事务的更新就要排队等锁。 - 大事务更新了太多行。一个事务里更新了成百上千万行,持有大量锁,其他更新全被堵住。
- 索引失效导致行锁升级为表锁。innodb的行锁是基于索引的,如果
WHERE条件没走索引,mysql可能扫描大量行,锁的范围会扩大,最终表现为锁表。
排查方法很有套路:
sql复制SHOW PROCESSLIST;
看哪些线程的State列是Waiting for table metadata lock,或者updating后面有个数字在增大。已经卡死的线程,可以用:
sql复制KILL 线程ID;
如果要更详细地看锁等待情况,执行:
sql复制SHOW ENGINE INNODB STATUS;
在输出里找LATEST DETECTED DEADLOCK或TRANSACTIONS段落,能看到是什么语句持锁、什么语句在等待。
防锁表的核心不是学多少命令,而是养成几个习惯:事务要短平快,及时提交;批量更新拆成小批次;WHERE条件尽量走索引;不要长时间持有一个连接然后才提交事务。
4. UPDATE排序去重与常用函数:高频语法一次讲透
4.1 UPDATE语法和最容易忽略的WHERE
mysql的UPDATE语法看起来简单,实际上因为少了WHERE而出事的案例比比皆是。
标准语法:
sql复制UPDATE 表名 SET 列名 = 新值 WHERE 条件;
比如把学号为20240001的学生姓名改掉:
sql复制UPDATE student SET student_name = '张三' WHERE student_no = '20240001';
如果漏了WHERE,结果就是全表所有学生的姓名都变成了“张三”。这不是段子,我见过不止一个同事在生产环境干过这事,最后靠备份恢复数据。
一个实用的防御技巧:在mysql客户端里,用--safe-updates模式启动,或者登录后执行:
sql复制SET SQL_SAFE_UPDATES = 1;
这种模式下,不带WHERE条件的UPDATE和DELETE会被mysql拒绝执行,相当于多了一道保险。当然它也会阻止一些合法的全表更新操作,所以用完记得改回来。
实战里还有个更隐蔽的坑:UPDATE后面跟着多表关联。mysql支持这种写法:
sql复制UPDATE student s
JOIN score sc ON s.student_id = sc.student_id
SET s.student_name = CONCAT(s.student_name, '(已选课)')
WHERE sc.course_id = 1;
这种语法改起来影响范围更大,执行前务必先SELECT同样的条件确认行数,养成“先查后改”的习惯,这是dba和资深开发的基本素养。
4.2 ORDER BY排序与DISTINCT去重
排序用ORDER BY,也有两个高频点。
一个是多字段排序:
sql复制SELECT * FROM score ORDER BY course_id ASC, score DESC;
先按课程升序,课程相同的再按成绩降序。这个顺序很多人搞反,以为DESC是修饰整条语句的,其实DESC/ASC只修饰它紧挨着的那个字段。
另一个是中文排序的坑。mysql默认按字符集排序,中文排序不是按拼音,而是按字符编码。如果你要按拼音排序,得指定排序规则:
sql复制SELECT * FROM student ORDER BY student_name COLLATE utf8mb4_zh_0900_as_cs;
注意,utf8mb4_zh_0900_as_cs这个排序规则只在mysql 8.0可用,5.7没有。5.7里更常见的做法是用CONVERT(student_name USING gbk)来排序,利用gbk编码的特性实现拼音序。
去重用DISTINCT,一个高频疑问是“mysql的or能去重吗”。答案是不能,OR只是多条件查询逻辑,跟去重没关系。去重必须显式用DISTINCT:
sql复制SELECT DISTINCT course_id FROM score;
DISTINCT会对返回的所有列组合去重。比如SELECT DISTINCT student_id, course_id FROM score,只有当两个字段值都相同才算重复行。它放在SELECT后面,作用范围是整个结果集的所有列,不是单独一列。
4.3 常用函数与int+5的隐式转换
mysql内置函数很多,实际开发中最高频的也就那十来个:
- 字符串:
CONCAT拼接、SUBSTRING截取、REPLACE替换、LENGTH长度。 - 日期:
NOW()当前时间、DATE_FORMAT格式化、DATE_ADD加天数。 - 数值:
ROUND四舍五入、ABS绝对值、CEIL/FLOOR向上向下取整。 - 条件:
IFNULL(expr, 默认值)空值替换、IF(条件, 真值, 假值)条件判断。
举一个成绩表常用但容易错的例子:筛选出成绩在60分以上、且考试日期最近的记录。
sql复制SELECT student_id, MAX(exam_date) AS latest_date
FROM score
WHERE score >= 60
GROUP BY student_id;
WHERE里不能直接用MAX(exam_date),因为聚合函数要配合HAVING使用。这是新手特别容易搞混的点。
热搜里那条“mysql中int+5”,如果是在SQL里写5 + 5,那结果就是数学运算,返回10,没有任何问题。真正值得警惕的是字段类型的隐式转换。比如:
sql复制SELECT * FROM student WHERE student_no = 20240001;
如果student_no是VARCHAR类型,mysql会尝试把两边转成浮点数比较,一旦字符串里有非数字字符,转换规则就很容易出问题。正确的做法是老老实实加引号:
sql复制SELECT * FROM student WHERE student_no = '20240001';
涉及函数、类型转换、索引,这三件事撞在一起时,索引往往会失效。比如对字段使用了函数WHERE DATE(created_at) = '2024-01-01',就无法利用created_at上的索引,应该改成范围查询WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02',效果等价但性能差别极大。
5. 存储过程与触发器的实用边界
5.1 存储过程的语法骨架与错误信息捕获
存储过程在mysql里的定位是“把一段业务逻辑封装在数据库端执行”。语法骨架是:
sql复制DELIMITER $$
CREATE PROCEDURE 过程名(IN 参数名 INT, OUT 结果名 VARCHAR(100))
BEGIN
DECLARE 变量名 INT DEFAULT 0;
SELECT COUNT(*) INTO 变量名 FROM student;
SET 结果名 = CONCAT('总人数:', 变量名);
END$$
DELIMITER ;
调用方式:
sql复制CALL 过程名(1, @result);
SELECT @result;
这里有两个关键的语法细节:
第一,DELIMITER。mysql默认用分号作为语句结束符,而存储过程体内部也有分号。如果不用DELIMITER $$临时把结束符改成$$,mysql客户端会在第一个分号处就以为语句结束了,导致语法错误。这是新手写存储过程报错的最常见原因,也是热搜里“mysql中触发器中分隔符”对应的核心知识点。
第二,存储过程中的错误信息捕获。写存储过程不写异常处理,等于裸奔。mysql里处理异常要靠DECLARE ... HANDLER:
sql复制DELIMITER $$
CREATE PROCEDURE 插入学生(
IN p_name VARCHAR(50),
IN p_no VARCHAR(20)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT '发生错误,事务已回滚' AS err_msg;
END;
START TRANSACTION;
INSERT INTO student(student_name, student_no) VALUES (p_name, p_no);
COMMIT;
END$$
DELIMITER ;
DECLARE EXIT HANDLER的作用是:当后续代码抛任何SQL异常时,立即执行BEGIN...END里的回滚逻辑,然后退出存储过程。这相当于给存储过程戴上了安全气囊。热搜词里“mysql储存过程+错误信息”指的就是这类问题,很多教程只教你写正常逻辑,不教异常处理,真用起来全是坑。
5.2 触发器和DELIMITER分隔符那点事
触发器是当某个表发生INSERT、UPDATE、DELETE操作时,自动执行的逻辑。一个最典型的业务场景:记录学生表的变更日志。
sql复制DELIMITER $$
CREATE TRIGGER trg_student_after_update
AFTER UPDATE ON student
FOR EACH ROW
BEGIN
INSERT INTO student_log(student_id, old_name, new_name, change_time)
VALUES (OLD.student_id, OLD.student_name, NEW.student_name, NOW());
END$$
DELIMITER ;
注意几个细节:
AFTER UPDATE表示在更新成功之后执行,还有BEFORE UPDATE、AFTER INSERT、AFTER DELETE等组合。OLD关键字表示更新前的旧值,NEW表示更新后的新值。FOR EACH ROW表示对每一行受影响的数据都执行一遍触发器逻辑,这是mysql触发器的工作方式,没有“语句级触发器”一说。- 创建触发器同样要用
DELIMITER命令,因为BEGIN...END;内部有分号,这个和存储过程一样。
触发器最容易被忽视的问题有两个:一是它在INSERT、UPDATE执行时同步运行,如果触发器里做了沉重操作(比如插入大量日志),会让主业务语句变得很慢;二是触发器之间不能互相嵌套,而且你很难调试,线上出了问题,排查成本非常高。
5.3 什么时候别用存储过程和触发器
存储过程和触发器不是不能用,而是要有节制。我的经验是:能用应用层解决的问题,就不要下放到数据库层。
存储过程适合的场景:批量数据处理、报表统计、需要事务边界的复杂业务逻辑。不适合的场景:简单增删改查、业务逻辑变化频繁的模块——因为存储过程的版本控制很弱,代码库里看不到明显变更记录,多人协作时容易失控。
触发器适合的场景:轻量级日志记录、数据归档、强制完整性约束。不适合的场景:引用了别的表的复杂逻辑、依赖外部状态的操作。触发器本质上是隐式逻辑,你查SQL是看不到它的,后来的维护者很容易漏掉这一层。
如果你负责的是长期维护的项目,我会建议:触发器和存储过程只用在真正能体现它们价值的地方,其余逻辑放到代码里,这样可读性和可维护性都更好。
6. 面试考点与实战经验的交汇
6.1 高频面试题背后的核心原理
mysql面试题,翻来覆去就是索引、事务、锁、引擎、Explain这几座大山。这些问题背答案没用,理解原理才是关键。
索引这一块的经典题目:为什么用了索引还是慢?原因通常是这几种:没遵守最左前缀原则、对索引列使用了函数或计算、隐式类型转换、用了LIKE '%xx'前置模糊查询。判断有没有走索引,看执行计划:
sql复制EXPLAIN SELECT * FROM student WHERE student_no = '20240001';
看type列,从const、ref、range到ALL,性能依次递减。如果看到ALL,说明是全表扫描,索引没有生效。这个技能比背十道面试题都管用。
事务与隔离级别是另一座大山。mysql默认隔离级别是REPEATABLE READ,这跟Oracle的默认READ COMMITTED不一样。为什么mysql这么设计?因为innodb的REPEATABLE READ通过MVCC实现了可重复读,同时不会出现幻读(准确说是大部分场景不会)。面试官爱问的“RR隔离级别下为什么没有幻读”,答案核心就是快照读和next-key lock的配合。
MVCC不必细抠,但要知道它是通过隐藏字段trx_id和roll_pointer实现多版本链,读操作不加锁也能看到一致性快照。这类题理解了以后,再去看“mysql的or能去重吗”“int+5”这种搜出来的问题,会觉得完全是小儿科。
锁的分类也常考:共享锁(S锁)、排他锁(X锁)、意向锁、记录锁、间隙锁、next-key锁。搞懂它们的根本逻辑就是:读读不互斥、读写互斥、写写互斥。间隙锁是为了防止在REPEATABLE READ级别下出现幻读而引入的,它锁住的是一个范围而不是具体记录。
6.2 docker安装mysql与数据持久化
现在很多开发环境直接用docker跑mysql,配置确实比本机安装要快得多。
bash复制docker run -d \
--name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=你的密码 \
-v /my/own/datadir:/var/lib/mysql \
mysql:8.0
几个参数解释一下:
-p 3306:3306,把容器的3306端口映射到宿主机的3306。-e MYSQL_ROOT_PASSWORD,指定root初始密码。-v /my/own/datadir:/var/lib/mysql,把容器里的数据目录挂载到宿主机。这一步最关键,没有它,容器一删,数据全没。
进容器执行命令:
bash复制docker exec -it mysql8 mysql -uroot -p
这里要特别提醒:如果本机3306端口已经被占用,先检查一下是不是之前启动过mysql服务。热搜里“docker安装mysql”之后连不上的案例,一半是因为端口冲突,一半是因为没有做数据卷挂载,删容器后数据丢失,又不敢跟老板坦白。
如果把mysql跑在容器里,还建议加上--restart=always参数,宿主机重启后容器能自动启动,省去手动docker start的麻烦。
6.3 从linux安装到项目对接的完整链路
linux上安装mysql,debian/ubuntu系和centos系的命令不同。以ubuntu为例:
bash复制sudo apt update
sudo apt install mysql-server
sudo systemctl status mysql
安装完成后,默认root用户用的是auth_socket插件,本机用sudo mysql可以直接登录,但用密码登录却不行。需要执行:
sql复制ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';
这一步是linux版mysql安装教程里最容易漏的,漏了之后用navicat远程连的时候会一直报Access denied,明明密码对了也进不去。
项目对接层面,热搜里有“vue项目 node 链接mysql”和“javaweb项目完整案例mysql”,说明现在全栈项目前端到后端再到数据库的链路已经是很普遍的需求。
node项目连接mysql,最常用的是mysql2这个驱动:
javascript复制const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: '你的密码',
database: 'school_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
const [rows] = await pool.query('SELECT * FROM student WHERE student_id = ?', [1]);
console.log(rows);
注意?占位符的用法,这是防SQL注入的标准姿势,绝不能用字符串拼接SQL。
javaweb项目则是在pom.xml里引入mysql-connector-java依赖:
xml复制<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
连接串写法:
code复制jdbc:mysql://localhost:3306/school_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
serverTimezone参数不设置会报时区错误,allowPublicKeyRetrieval不设置,连接8.0时可能报Public Key Retrieval is not allowed,这两个参数是java连接mysql高频翻车点。
不管用什么语言连接mysql,思路都是一样的:建立连接池、使用预编译占位符、用完归还连接。理解了这套逻辑,node、java、python、go只是语法上的差异。
写到这里,这篇第10篇笔记的内容算是收住了。回头看这10篇,我最想强调的还是那句话:mysql不是背出来的,是一遍一遍踩坑踩出来的。从安装时的版本选择,到第一次连不上数据库的抓狂,再到亲手设计出第一张符合三范式的关系表——每个阶段都有每个阶段值得记录的东西。如果你在安装配置、连接报错、表结构设计或者面试准备的过程中按图索骥解决了问题,那这篇笔记就没白写。下一轮我打算把《Mysql--10》里没展开的在线改表工具和锁等待监控补充成一篇独立的实战记录,如果你正好也被锁表折磨过,可以提前准备一个测试库,到时候一起试。
