最近几年带新人,我发现一个规律:几乎每个后端项目都绕不开 MySQL,但真正能把 MySQL 用顺的人其实不多。很多人一上来就背面试题、看架构图解,结果连本地的 MySQL 都起不来,更别提处理锁表、慢查询这些实际问题了。我这些年攒了不少笔记,结合自己踩过的坑,整理成一篇以“能落地”为目标的 MySQL 实操笔记,从安装部署讲到 SQL 实战,再讲到索引、存储过程、连接排查和面试考点,新手可以按顺序看,有经验的也可以直接跳到感兴趣的部分。
这篇笔记以 MySQL 8.0 为主线(目前最稳的长期支持版本),覆盖 Windows、Linux、Docker 三种部署方式,日常 SQL 高频用法,索引与 explain,存储过程,以及 Workbench、Navicat 连接时的常见报错。适合刚入门想系统学习数据库的人,也适合后端开发、运维人员当速查手册用。
1. 环境准备:三种最常见的 MySQL 8.0 部署方式
1.1 Windows 免安装版:5 分钟起一个干净实例
很多朋友第一次接触 MySQL 是在 Windows 上,下载时面对官网一堆入口容易懵。官网 Downloads 页面往下翻到 MySQL Community Server,选择 ZIP Archive 版本即可,这就是大家常说的“免安装版”。下载完解压到 D:\mysql-8.0.xx,真正的安装工作其实才刚刚开始。
这里说一个最容易被忽略的坑:MySQL 8.0 默认的认证插件是 caching_sha2_password,很多老工具连不上都是因为这个。不过我们先解决“能不能启动”的问题。解压后建议手动新建一个 my.ini(放在解压根目录下),至少写上 basedir 和 datadir 两个路径配置,例如:
[mysqld] basedir=D:/mysql-8.0.43 datadir=D:/mysql-8.0.43/data port=3306注意路径分隔符用正斜杠,路径里尽量不要有中文和空格。写完 my.ini 后,用管理员身份打开 CMD,先执行 mysqld --initialize-insecure。这个命令会初始化数据目录,--insecure 表示生成的 root 账号初始密码为空,方便后续登录后再改。如果你用 mysqld --initialize,系统会随机生成一个强密码并写到 data 目录的日志文件里,新手经常找不到,所以我更推荐先用 insecure 方式,避免一开始就被临时密码卡住。
接着执行 mysqld --install 注册成 Windows 服务,再 net start mysql 启动。如果服务启动不了,Windows 事件查看器里的 MySQL 日志是最直接的线索,常见原因无非是 my.ini 路径写错、端口被占用、data 目录权限不对这几类。启动成功后,用 mysql -uroot -p 登录,密码直接回车,然后立刻执行 ALTER USER 'root'@'localhost' IDENTIFIED BY '你的密码'; 把空密码改掉。实测下来,8.0 在 Windows 上最稳的就是这套流程,比图形安装包少了注册表残留的隐患,想卸载时直接停服务、删目录就行。
1.2 Linux 下用包管理器安装:临时密码和 socket 报错
Linux 服务器上装 MySQL 是后端开发和运维的必修课。CentOS 系用 dnf/yum,Debian/Ubuntu 系用 apt。要注意的是,不同发行版自带源里的包名和版本差别很大,如果系统源里不是官方 MySQL 而是另一个兼容分支,建议先去官网配置对应的官方 repo,保证拉到的是 MySQL 8.0,避免后续行为差异带来的困惑。
安装之后,服务可能不会自动启动。常见流程是 systemctl start mysqld,然后执行 grep 'temporary password' /var/log/mysqld.log 查看临时密码。这一步和 Windows 的 initialize 思路类似:MySQL 8.0 在 Linux 上默认会生成随机 root 密码并藏在日志里,登录进去第一件事必须改密码,否则任何业务操作都做不了。如果你的日志文件里找不到临时密码,大概率是之前已经初始化过,需要检查数据目录里是否已有文件。
Linux 上还有一个高频报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'。这个错误的本质是客户端连不上 mysqld 进程,而不是密码问题。先 systemctl status mysqld 确认服务状态;如果服务没起来,多半是数据目录权限不对,或者磁盘满了。如果服务起来了还报这个错,检查配置里的 socket 路径是否一致,再看 /var/run/mysqld 目录是否存在。目录不存在就手动创建并改成 mysql 用户所有,这样才能让 mysqld 正常写入 socket 文件。
1.3 Docker 安装 MySQL:开发测试环境利器
如果只是本地开发、跑测试用例,我强烈建议用 Docker。命令就一条:
docker run --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0拉取官方镜像后会自动初始化数据目录,MYSQL_ROOT_PASSWORD 就是 root 的初始密码,这是 Docker 镜像和物理安装最直观的区别:不需要看日志找临时密码。默认情况下,root 账号只允许从容器内部连接,但因为我们映射了端口,从宿主机通常也能连。为了安全,建议加 -v 参数做数据持久化:
docker run --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 \ -v mysql_data:/var/lib/mysql -d mysql:8.0数据卷挂载是最容易被忽略的细节。MySQL 容器一旦删掉,如果没有挂载数据卷,库里的数据会全没。我见过不少同事因此吃了大亏。另外,官方镜像默认时区是 UTC,建表后用 NOW() 插入时间会差 8 小时,建议加 -e TZ=Asia/Shanghai。如果用的是 docker-compose,就把环境变量和 volume 都写进 yml 文件里,方便团队统一复现。
Docker 版本还有一个经典问题:外部 Navicat 或 Workbench 连接不上。排除防火墙和端口映射后,大概率是认证插件不兼容。可以在容器里执行 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '密码'; 把认证方式改掉。虽然 8.0 官方推荐 caching_sha2_password,但不少老版本客户端确实只支持 mysql_native_password,这个根据实际工具版本灵活处理就好。
2. 日常高频 SQL:从命令行到实战场景
2.1 数据库命令大盘点
命令行是 MySQL 最可靠的面板,任何图形工具报错时,回到命令行测一遍能帮你快速定位是 SQL 问题还是工具问题。下面这些命令是我日常用得最多的:show databases; 查看库列表,use 库名; 切换库,show tables; 查看表列表。表结构不清楚时用 desc 表名; 或者 show create table 表名;,前者看字段概览,后者能看到建表语句全文、索引、字符集和字段注释,排查线上表结构差异时格外有用。
系统信息查询里,select version(); 确认数据库版本,status; 看当前连接状态和字符集。字符集乱码是新手高频问题,建议统一用 utf8mb4,建库时就指定:create database xxx default character set utf8mb4 collate utf8mb4_general_ci;。utf8mb4 是真正的四字节 UTF-8,能存 emoji,而老的 utf8 是 MySQL 自己的三字节实现,碰到 emoji 会报错或变成问号。这个细节在表设计阶段就要定下来,否则后期改字符集成本很高。
查询用户账号的命令是 select user, host from mysql.user;。这里的 host 含义值得展开:localhost 表示只能本机连,% 表示任意主机,192.168.1.% 表示指定网段。线上设置账号权限时,尽量精确到主机或网段,不要图省事一律用 %,这样可以缩小密码泄露后的可连接范围。还记得有一次线上数据库被扫到弱口令,因为用户权限是 %,攻击者直接从外网爆破成功,教训非常深刻。
2.2 排序与分组:排序失效和行转列经典写法
排序是面试里喜欢问、实战里容易错的点。select * from t order by create_time desc limit 10; 这种写法大家都会,但要知道 MySQL 8.0 的优化器对 order by 的处理是有讲究的:如果 order by 的字段走了二级索引,排序会被自动优化掉,MySQL 会顺着索引顺序读数据,不用额外的 filesort。反过来,如果排序字段没有索引,或者查询条件导致驱动表的选取逻辑发生变化,就会触发 filesort,数据量大时排序性能会明显下降。
group by 的坑比 order by 更多。建议记住一条原则:select 中出现非聚合列时,这些列必须包含在 group by 中。比如 select dept_id, name, count(*) from emp group by dept_id; 在 MySQL 里能执行成功,前提是默认 sql_mode 没开启 ONLY_FULL_GROUP_BY,但 name 取的是组内随机一条,结果不可控。这个行为在 5.7 之后默认被禁用,报错信息会明确提示 which isn't in GROUP BY。遇到报错时,要么把 name 加进 group by,要么用 any_value(name) 明确表达“我就是要随机取一个”,避免歧义。
行转列是另一个常被问到的场景。比如有一张学生成绩表,字段是 student_id、subject、score,要把三科成绩从竖排变成横排,经典写法是:
select student_id, max(case when subject='语文' then score end) as chinese, max(case when subject='数学' then score end) as math, max(case when subject='英语' then score end) as english from score group by student_id;为什么这里用 max?因为 group by 后每个学生只保留一行,case when 只在对应科目时取值,其他行是 null,再用 max 去除非 null 值。用 min 也可以,只要分组内的目标值非空,聚合函数就会保留它。这是标准的 group by + 条件聚合写法,比动态拼接 SQL 更安全,也是面试的加分项。之前我还见过有人用多个 left join 自连接实现同样的效果,行数一多性能会急剧下降,条件聚合的方式明显更干净。
2.3 更新子查询:绕开“不能更新目标表”的限制
更新子查询是热搜里一个非常具体的场景。MySQL 对 update 语句的子查询有一个限制:不允许直接更新正在被读取的表。比如想给低于平均工资的员工涨薪,直接写:
update emp set salary = salary * 1.1 where salary < (select avg(salary) from emp);会报错:You can't specify target table 'emp' for update in FROM clause。原因是 MySQL 执行 update 时,子查询和更新目标指向同一张表,会造成读取和修改的交叉,优化器直接拒绝。
解决办法是包一层派生表,让子查询先物化成一张临时表:
update emp set salary = salary * 1.1 where salary < (select avg_salary from (select avg(salary) as avg_salary from emp) t);这里的内层 select 先算好平均工资并产生内存临时表,外层 update 读临时表而不是直接读 emp,就绕开了限制。这个“包一层”的技巧在 delete 语句里同样适用,原理完全一样。实际开发中,如果更新逻辑比较复杂,我更建议先 select 出要更新的主键列表,交给应用层处理,既能避免这种语法限制,又方便记录操作日志。
3. 索引、执行计划与锁表排查
3.1 创建索引:不是越多越好
索引是 MySQL 性能优化的核心,也最容易“建错”。创建索引的语法很简单:
create index idx_name on table(column); create unique index idx_uk on table(column);但怎么建、建在哪些列上,是有讲究的。核心原则是:索引要匹配查询条件。一条 SQL 里的 where 条件、join 关联字段、order by / group by 字段,都是候选索引列。而 select 之后出现的字段,通常不需要单独建索引。最典型的索引失效场景是:在索引列上用了函数或计算。比如 where year(create_time) = 2024 和 where create_time >= '2024-01-01' and create_time < '2025-01-01',前者即使 create_time 上有索引也用不上,因为索引存的是原始值,MySQL 必须在每一行上做函数运算才能完成比较。
联合索引要遵循最左前缀原则。建了 (a, b, c) 联合索引,a、a+b、a+b+c 三种查询条件都能用上索引,但只查 b 或只查 c 就用不上。设计联合索引时,通常把等值查询字段放前面,范围查询字段放后面。比如 status = 1 and create_time > '2024-01-01' 这种组合,建议建 (status, create_time),因为 status 是等值匹配,create_time 是范围匹配,这样能最充分地利用索引的排序特性。
索引还有隐性开销:每次插入、更新、删除,索引都要同步维护。索引建得太多,写性能会被拖慢,磁盘占用也会膨胀。我的经验是:单表索引数量控制在 5 个以内,重复索引及时清理。很多表里既有 idx_a_b(a,b) 又有 idx_a(a),后者其实是冗余的,可以删掉。这个习惯能帮你省掉很多不必要的存储成本和维护成本。
3.2 explain 执行计划:慢 SQL 排查第一课
排查慢 SQL,第一步永远是 explain。在 SQL 前加 explain 关键字,MySQL 会返回执行计划,而不是真正执行 SQL。重点看这几列:
- type:访问类型,从好到坏大致是 system、const、eq_ref、ref、range、index、ALL。ALL 是全表扫描,range 是范围扫描,ref 是普通等值匹配,const/eq_ref 是主键或唯一索引的等值匹配。看到 ALL 就要警惕。
- key:实际用到的索引名。如果为 null,说明这条 SQL 没走索引。
- rows:优化器预估扫描的行数,越少越好。这只是基于统计信息的估算值,不是真实值。
- Extra:额外执行信息。出现 Using filesort 或 Using temporary 时,通常意味着 order by 或 group by 没有利用索引,需要重点优化。
举一个真实例子。一条查询两百万行订单表的 SQL,explain 后 type 是 ALL,rows 是 210 万,Extra 里还有 Using filesort。加了一个 (uid, status) 联合索引后,type 变成 ref,rows 降到几百,查询从 2.3 秒降到 40 毫秒。这个量级的提升在调优中非常常见,也说明执行计划对性能定位的重要性。
explain 还有一个进阶用法:explain analyze(MySQL 8.0 新增)。它不但给执行计划,还会真实执行并输出每一步的耗时和行数。相比 explain 的估算值,explain analyze 是实测结果,定位真实耗时瓶颈时更可靠。不过线上大表用 explain analyze 要谨慎,因为它会真的执行 SQL,可能在慢 SQL 基础上加重数据库负载。
3.3 锁表与阻塞:遇到 1205 超时怎么办
锁表是生产环境的常见事故。现象是业务操作卡住,报 Lock wait timeout exceeded(错误码 1205)或者 Deadlock found(错误码 1213)。先解释锁的两种常见类型:行锁和表锁。InnoDB 引擎默认使用行锁,但注意行锁要建立在索引上,如果 where 条件没有索引,InnoDB 会退化成锁住全表,这就是“无索引导致锁表”的经典成因。所以排查线上性能问题时,看到更新语句走了全表扫描,要第一时间考虑锁范围扩大的风险。
排查锁等待的第一步是 show processlist;,看有没有长时间处于 Waiting for table metadata lock 或 Waiting for lock 状态的会话。如果发现有会话卡了很久,找到对应的 id,用 kill id; 杀掉它。还有一种情况是事务未提交,持锁不释放,这时候要查 information_schema.innodb_trx 表,找到 trx_state 是 RUNNING 且耗时很长的记录,结合 trx_mysql_thread_id 杀掉对应线程。这个表能直接看到事务开启时间,比盲目猜代码快得多。
1213 死锁和 1205 超时的处理思路略有不同。死锁是多事务互相持有对方需要的锁,MySQL 会自动回滚其中一个事务并让另一个继续,应用层收到死锁错误后重试即可。超时则是锁等待时间超过 innodb_lock_wait_timeout(默认 50 秒),说明持有锁的事务长时间没结束。此时优先排查那个事务为什么没提交:是代码里忘了 commit,还是事务内做了慢查询,又或者是长事务跨越了多次远程调用。把这些写进代码评审规范,能避免大部分锁问题。
4. 存储过程:什么时候用、怎么写
4.1 先搞懂适用场景,别滥用
存储过程是 MySQL 里一段预编译的 SQL 集合。很多人刚学时觉得它高大上,其实现在业务开发里存储过程的使用越来越少,因为业务逻辑放在应用层更易维护、更易测试。但有几个场景仍然绕不开:复杂的批量数据加工、定时任务里的报表运算、历史数据归档,以及一些对往返时延敏感且需要减少网络交互的写操作。
不建议一上来就在业务系统里大量使用存储过程。我见过最头疼的维护场景是:几百行的存储过程没有注释、没有版本管理,里面还有隐式事务问题,出问题时只能连到生产库一步步调试,效率极低。但如果你是做数据处理、ETL、报表相关的开发,存储过程还是得会写,因为这类任务经常要和数据库强绑定,存储过程能减少大量中间层的传输开销。
4.2 一个完整的存储过程示例
写存储过程前先记住一个关键语法:DELIMITER。MySQL 默认用分号作为语句结束符,而存储过程内部有多个分号语句,如果不重新定义结束符,客户端会在第一个分号处提前截断,导致创建失败。通常的做法是:
DELIMITER // create procedure batch_update() begin declare done int default 0; declare v_id int; declare cur cursor for select id from emp where salary < 5000; declare continue handler for not found set done = 1; open cur; read_loop: loop fetch cur into v_id; if done then leave read_loop; end if; update emp set salary = salary * 1.1 where id = v_id; end loop; close cur; end // DELIMITER ;这里有几个要点。declare 声明变量必须在存储过程体最前面集中声明;游标 cur 是逐行读取结果集的方式;continue handler for not found 是在游标读取完时把 done 置为 1,循环检测到 done 后退出。调用方式很简单:call batch_update();。
实际使用中,我不推荐在循环里一条条 update,性能太差。上面的例子里,每条 update 都要单独走一次索引查找和行锁操作,两万行数据就要执行两万次,效率非常低。更好的做法是直接用一条 update 完成批量操作:update emp set salary = salary * 1.1 where salary < 5000;。存储过程的价值在于处理那些“多条 SQL 必须按顺序执行且有依赖”的逻辑,比如先判断条件再决定是插入还是更新,这种流程控制才是它真正的用途。
还有一个容易忽略的点:存储过程里的多语句不会自动开启事务。默认每条 SQL 都是自动提交,如果中间某条语句失败,前面的修改已经生效,再回滚就来不及了。要保证数据一致性,需要在 begin 里显式加 start transaction;,并在所有语句执行成功后加 commit;,失败时加 rollback;。这是生产环境用存储过程最需要谨慎的地方。
5. 客户端工具与连接问题排查
5.1 Workbench 与 Navicat 的基础用法
图形化客户端是日常开发的好帮手。MySQL 官方提供的 MySQL Workbench 免费,功能全面,支持可视化建表、ER 图、SQL 编辑、导入导出,新手完全可以用它入门。Navicat 的交互体验更顺滑,支持多数据库切换,但它是商业软件,建议使用正版授权,团队协作时也能避免授权风险。
Workbench 第一次连接时填 host、port、user、password 就行。界面左边是 SCHEMAS 面板,能看到所有库表;右键表名可以快速查看行数、查看建表语句、复制表结构。写 SQL 时,点查询菜单下的“执行”按钮,运行的是当前选中的语句。很多人一进来没有选中任何语句直接执行,结果发现跑了一大堆 SQL,甚至导致误操作——这个工具的逻辑是“执行选中部分,未选中则执行全部”,搞清楚这一点就不会误操作了。
Navicat 连接时的配置更直观,主机、端口、用户名、密码填完就能连。导入导出时,字符集记得选 utf8mb4,否则中文容易乱码。另外,两个工具连接本地 MySQL 时,如果填 localhost 走的是 socket 连接,填 127.0.0.1 走的是 TCP 连接。后者更接近真实网络环境,排查问题时建议优先用 127.0.0.1,这样能把 socket 相关变量排除在外。
5.2 error 2002 与连接报错的完整排查流程
ERROR 2002 是热搜里出现太多次的问题,值得单独说透。完整报错是 ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'。这个报错的核心是:客户端通过 Unix socket 文件连接 MySQL,但找不到这个 socket 文件,或者 mysqld 进程根本没起来。
排查顺序固定为三步。第一步,确认服务状态:systemctl status mysqld 或 ps -ef | grep mysqld,服务没起来就查日志,日志位置一般在 /var/log/mysqld.log。第二步,确认 socket 路径:客户端连接时用默认路径,但 mysqld 实际写的 socket 路径可能被改过,检查 /etc/my.cnf 里的 socket 配置是否一致。第三步,检查权限:mysql 用户对 socket 所在目录要有读写执行权限,目录不存在时手动 mkdir -p /var/run/mysqld && chown mysql:mysql /var/run/mysqld。
Navicat 或 Workbench 连接超时是另一个高频问题。先 ping 一下服务器 IP,再 telnet 服务器IP 3306 端口。如果端口不通,检查防火墙;如果端口通但连接报认证错误,看是不是 caching_sha2_password 导致老客户端不支持,解决办法就是前文提到的改成 mysql_native_password。还有一种情况:MySQL 配置了 bind-address=127.0.0.1,只监听本地,外部无法连接,需要注释掉或改成 0.0.0.0 并重启服务。
6. 面试高频考点与 MySQL 架构地图
6.1 一条 SQL 的执行流程
理解 MySQL 架构对排查问题、读懂报错都很有帮助。整体分两层:Server 层和存储引擎层。Server 层负责连接管理、语法解析、优化和执行,存储引擎层负责数据读写。一条查询 SQL 的执行流程大致是:连接器校验用户名和权限,分析器做词法语法解析,优化器决定用什么索引、怎么关联,执行器调用存储引擎接口取数据,最后返回结果集。
这个流程解释了为什么索引会影响查询速度:优化器在决定执行计划时,会根据统计信息估算不同索引的代价,选一个它认为最优的方案。有时候优化器会选错索引,可以用 force index 强制指定想要的索引,但更推荐先 analyze table 更新统计信息,让优化器自己重新判断。毕竟统计信息过期才是选错索引的常见原因。
update 语句的流程比查询多几个环节:要先把数据页从磁盘读进缓冲池,在缓冲池中修改,然后写 redo log(保证崩溃恢复),同时记录 binlog(用于复制和回溯)。这里涉及经典的“两阶段提交”机制,保证 redo log 和 binlog 的一致性。这也是 MySQL 具备 crash-safe 能力的原因:即使数据库突然宕机,重启后也能靠 redo log 恢复未落盘的数据。理解了这套流程,再看主从复制延迟、数据回滚这些问题时会通透很多。
6.2 面试里最容易被问倒的几个点
面试环节里,MySQL 几乎必考几个问题。第一个是“索引为什么用 B+ 树而不用哈希表”。哈希表等值查询是 O(1),但无法支持范围查询,也无法排序;B+ 树所有数据都存储在叶子节点,叶子节点通过链表串联,天然支持范围扫描和排序,而且树的高度很低,三层 B+ 树就能存上千万条记录,磁盘 IO 次数少。这个对比是理解索引实现的根基。
第二个考的是回表。select * from user where name = '张三',如果只在 name 字段建了普通索引,那么通过索引找到主键 id,再用主键 id 回聚簇索引查完整行,这个二次查询就是回表。如果查询需要的列全部包含在索引里,那就叫覆盖索引,不需要回表,性能更好。所以建索引时可以适当把高频查询字段加进联合索引,实现覆盖,减少一次主键查找。
第三个是 MVCC(多版本并发控制)。InnoDB 通过 undo log 保存数据的历史版本,配合事务的 ReadView 机制,实现不同隔离级别下的可见性判断。默认的隔离级别是可重复读,事务开启时会生成一个快照,整个事务期间读到的都是这个快照的数据,从而保证同一事务内多次查询结果一致。理解 MVCC 之后,很多“为什么我插入了数据,另一个事务读不到”的问题就迎刃而解了。
6.3 一份务实的 MySQL 学习路线
最后给想系统学习 MySQL 的朋友一条路线建议。第一步,先把安装部署和命令行用熟,能建库、建表、增删改查,掌握索引、视图、事务这些基础概念,对应本文前面的章节就够用了。第二步,学 SQL 优化和数据库设计,重点练习 explain 和慢查询分析,学会设计规范化的表结构,理解三范式但不要过度设计,适当冗余反而能提升复杂查询的性能。
第三步,深入学习 InnoDB 引擎原理,包括事务隔离级别、锁机制、MVCC、redo/undo/binlog 日志体系。到了这个阶段,可以开始看官方文档的 InnoDB 章节,配合真实问题反复验证。第四步,结合生产实践学高可用和运维:主从复制、读写分离、备份恢复、监控告警。这些内容需要实际环境和长期实践来沉淀,不是看完文档就能掌握的。
最后分享一点个人体会。我见过太多人把 MySQL 学习当成“背题”工程,各种命令、原理背得滚瓜烂熟,一上手却连 explain 都不会看。数据库这个东西,纸上得来终觉浅,最好的方式就是本地起一个实例,把本文提到的场景全部亲手执行一遍:装一遍三种部署方式、造两万条数据测索引效果、写一个带游标的存储过程、故意制造一次锁等待再排查。你自己踩过的坑,往往比任何教程都记得牢。
这篇笔记还有很多方向没有展开,比如主从复制、分库分表、慢日志分析、备份恢复策略,这些都是生产环境一定会用到的内容。后续我会把实践过程中继续沉淀下来的内容,按主题一篇篇补充进来,让这份“MySQL 笔记”真正变成一份能陪着你从入门到实战的系列资料。