feat:初始化工程
Some checks failed
Deploy Vitepress to Gitea Pages / build (push) Has been cancelled
Deploy Vitepress to Gitea Pages / deploy (push) Has been cancelled

This commit is contained in:
2025-11-26 15:16:03 +08:00
commit 76683b470d
14 changed files with 2603 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
import { defineConfig } from 'vitepress'
// https://vitepress.dev/reference/site-config
export default defineConfig({
lang: 'zh-CN',
title: "Cxx0822",
description: "Cxx0822 Blog",
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Web', link: '/Web/' },
{ text: 'DevOps', link: '/DevOps/' }
],
sidebar: {
'/Web/': [
{
text: 'Web',
collapsible: true,
items: [
{ text: 'MySQL知识点', link: '/Web/MySQL知识点' }
]
},
],
'/DevOps/': [
{
text: 'DevOps',
collapsible: true,
items: [
{ text: 'OpenObserve部署和简单使用', link: '/DevOps/OpenObserve部署和简单使用' },
{ text: 'Jenkins部署和使用', link: '/DevOps/Jenkins部署和使用' }
]
}
],
},
// socialLinks: [
// { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
// ]
footer: {
message: '基于 VitePress 构建',
copyright: 'Copyright © 2025 Cxx0822'
},
lastUpdated: {
text: '最后更新于',
formatOptions: {
dateStyle: 'full',
timeStyle: 'medium'
}
},
search: {
provider: 'local',
options: {
translations: {
button: {
buttonText: '搜索文档',
buttonAriaLabel: '搜索文档'
},
modal: {
noResultsText: '无法找到相关结果',
resetButtonTitle: '清除查询条件',
footer: {
selectText: '选择',
navigateText: '切换',
closeText: '关闭'
}
}
}
}
},
// 返回顶部按钮配置
returnToTop: {
// 显示返回顶部按钮的阈值(滚动距离)
threshold: 300,
// 按钮文本
text: '返回顶部'
},
// 上下篇导航
docFooter: {
prev: '上一篇',
next: '下一篇'
},
}
})

View File

@@ -0,0 +1,112 @@
# 一、Jenkins安装
## 1.1 简介
  Jenkins 是一款开源的持续集成Continuous Integration, CI 和持续交付Continuous Delivery, CD 自动化工具,广泛应用于软件开发流程中,帮助团队实现代码构建、测试、部署的自动化,从而提升开发效率、减少人为错误,并确保软件质量。
## 1.2 下载使用
1. 打开[官网](https://www.jenkins.io/zh/)选择版本下载本文版本为Version 2.504.2)。
2. 输入指令直接启动:
```cmd
java -jar jenkins.war
```
  第一次需要输入密码,在控制台中可以看到。
## 1.3 系统服务启动
1. 将jenkins.war复制到相应位置例/usr/share/jenkins/jenkins.war
2. 在/etc/systemd/system中新建系统服务文件 jenkins.service
```service
[Unit]
Description=Jenkins Continuous Integration Server
After=network.target
[Service]
Type=simple
User=root
Group=root
ExecStart=/usr/bin/java -jar /usr/share/jenkins/jenkins.war
Restart=always
[Install]
WantedBy=multi-user.target
```
3. 如需配置Nginx反向代理ExecStart启动命令后增加配置信息
```cmd
ExecStart=/usr/bin/java -jar /usr/share/jenkins/jenkins.war --httpPort=9090 --prefix=/jenkins
```
4. 更改nginx配置文件
```cmd
server {
listen 80 default_server;
listen [::]:80 default_server;
# Jenkins服务
location /jenkins {
proxy_pass http://127.0.0.1:9090/jenkins;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
  这里的代理端口号需要和ExecStart启动命令的端口号一致。
5. 重启服务:
```cmd
sudo nginx -s reload
systemctl daemon-reload
systemctl restart jenkins.service
```
## 1.4 插件安装和配置
  选择Dashboard -> Manage Jenkins -> Plugins安装Maven、Node、Gitee等插件。
### 1.4.1 配置JDK
1. 安装Java JDK 17`sudo apt install openjdk-17-jdk`
2. 选择Dashboard -> Manage Jenkins -> Tools点击Add JDK输入Name和JAVA_HOME
```cmd
Name: JDK 17
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
```
  注:根据实际情况修改,下同。
### 1.4.2 配置Maven
1. 官网下载Maven压缩包解压至/opt目录
2. 选择Dashboard -> Manage Jenkins -> Tools点击Add Maven输入Name和Maven_HOME
```cmd
Name: Maven3.9.10
Maven_HOME: /opt/apache-maven-3.9.10
```
### 1.4.3 配置NodeJS
1. 官网下载NodeJS压缩包解压至/opt目录
2. 选择Dashboard -> Manage Jenkins -> Tools点击Add Node输入Name和Installation directory
```cmd
Name: Node20.10.0
Installation directory: /opt/node-v20.10.0-linux-x64
```
### 1.4.4 配置Gitee
  选择Dashboard -> Manage Jenkins -> System选择Gitee Configuration输入name和URL点击Add Credentials选择Kind的Gitee API token将Gitee中生成的token复制到指定位置。
  该版本新增的Gitee Token并不能在后续的项目中找到但是又无法新增username和password形式只能在项目的gitee配置中新增username和password的形式不知道是什么原因
# 二、创建工程
## 2.1 创建Maven工程
1. 选择Dashboard -> New Item
2. 输入项目名称
3. 选择Maven Project
4.
## Springboot项目
```cmd
BUILD_ID=dontKillMe
cp /home/estun/.jenkins/workspace/sweet-hut-service/home-service/target/home-service-2.0.0.jar /home/estun/EdgeProjects/sweet-hut-service
cd /home/estun/EdgeProjects/sweet-hut-service
sh spring-service.sh restart home-service-2.0.0.jar
```
## Vue3项目
```cmd
npm install
npm run build
cp -rf ./dist/* /home/estun/EdgeProjects/sweet-hut-ui
```

View File

@@ -0,0 +1,214 @@
## 一、基础概念
### 1.1 Fluent Bit
  [Fluent Bit](https://fluentbit.io/) 是一个开源的、轻量级、高性能的日志处理器和转发器。Fluent Bit 的核心任务是:​​**从各种来源收集日志、指标和追踪数据,进行处理和过滤,然后将其发送到一个或多个目的地。​**
#### 1.1.1 特点
1. 极致的轻量级​
  Fluent Bit 是用 C 语言编写的,运行时内存占用极低(通常只有几百 KBCPU 使用率也非常低。这使得它成为在资源受限环境如边缘计算、IoT 设备、容器侧车)中运行的理想选择。
2. 高性能​
  其核心架构经过高度优化,处理数据的速度极快,能够轻松应对高吞吐量的日志流。
3. 可插拔架构​
  Fluent Bit 的功能通过“插件”来扩展。
#### 1.1.2 插件类型
1. Input输入定义数据来源例如从文件、标准输入、TCP/UDP、系统指标CPU/内存、Docker 容器、Windows 事件日志等收集数据。
2. Filter过滤器在数据传递过程中进行处理例如解析如将非结构化文本解析为 JSON、丰富如添加 Kubernetes 元数据)、修改(如重命名字段)、采样等。
3. Output输出定义数据目的地例如 Elasticsearch、Splunk、Kafka、Datadog、Loki、S3、另一个 Fluentd/Fluent Bit 实例等。
#### 1.1.3 核心架构与数据流
1. Input输入一个或多个输入插件持续地从数据源收集数据。每条数据被读入后会带上时间戳和标签等信息被包装成一个内部的事件记录。
2. Parser解析器可选输入插件可以使用解析器将非结构化的日志数据如 Nginx 访问日志)转换为结构化的 JSON 格式。
3. Filter过滤可选数据会经过一个或多个过滤器插件。这些插件可以修改、丰富或删除记录。过滤器是按顺序执行的。
4. Router路由根据每条记录的标签Tag路由器将其匹配到相应的输出插件。一个记录可以被路由到多个输出多路复用
5. Buffer缓冲在发送到输出之前数据可能会在内存或文件系统中进行缓冲。
6. Output输出最终输出插件将处理好的数据发送到配置的目的地。
### 1.2 OpenObserve
  [OpenObserve](https://openobserve.ai/) 是一个开源的、云原生的可观测性数据平台,旨在处理日志、指标、追踪和性能数据。它的目标是成为 Elasticsearch/Logstash/Kibana (ELK)Grafana LokiDatadog 等流行解决方案的​​更简单、更经济高效、更高性能​​的替代品。
  OpenObserve 是一个“一体化”的平台,你可以将所有的可观测性数据(日志、指标、追踪)都发送到它这里,然后通过统一的界面进行搜索、分析和可视化。
## 二、Docker部署
```yml
services:
fluent-bit:
image: fluent/fluent-bit:4.0.7
container_name: fluent-bit
restart: unless-stopped
volumes:
- ./fluent-bit/config:/fluent-bit/etc
ports:
- "24422:24422"
environment:
- TZ=Asia/Shanghai
networks:
- devops
openobserve:
image: public.ecr.aws/zinclabs/openobserve:v0.14.0
container_name: openobserve
restart: unless-stopped
volumes:
- ./openobserve-data:/data
environment:
- ZO_ROOT_USER_EMAIL=1556464090@qq.com
- ZO_ROOT_USER_PASSWORD=19940822Cxx
- TZ=Asia/Shanghai
ports:
- "5080:5080"
networks:
- devops
networks:
devops:
driver: bridge
volumes:
openobserve-data:
driver: local
```
  fluent bit配置文件
```conf
[SERVICE]
Flush 5
Log_Level info
Daemon off
Parsers_File parsers.conf
[INPUT]
Name forward
Listen 0.0.0.0
Port 24422
Tag external
[OUTPUT]
Name http
Match *
Host openobserve
Port 5080
URI /api/default/default/_json
http_user 1556464090@qq.com
http_passwd 19940822Cxx
Format json
json_date_key timestamp
json_date_format iso8601
```
  INPUT为日志输入源监听24422端口。
  OUTPUT为日志输出源将日志发送到openobserve中这里使用的是容器名称URI表示openobserve接收数据的api路径default/default表示组织名称/流名称可以更改为其他的名称。_json表示以json的格式。
## 三、简单使用
### 3.1 配置数据流
  打开控制台,选择数据流->添加数据流输入fluent bit配置文件中的流名称选择数据流类型为Logs。
  在新添加的数据流中,点击查看,跳转到日志页面即可看到上次的日志信息。
  如果没有日志请检查FluentBit容器日志是否有错误正确的应该会有上传成功的提示。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/a2cfe735a9245758daf6ea36a4c82f04.png '配置数据流.png')
### 3.2 配置邮件告警
  首先需要配置模板和邮箱地址。
  修改docker-compose.yml文件添加邮件服务器
```yml
openobserve:
image: public.ecr.aws/zinclabs/openobserve:v0.14.0
container_name: openobserve
restart: unless-stopped
volumes:
- ./openobserve-data:/data
environment:
- ZO_ROOT_USER_EMAIL=njcxx0822@163.com
- ZO_ROOT_USER_PASSWORD=19940822Cxx
- TZ=Asia/Shanghai
- ZO_SMTP_ENABLED=true
- ZO_SMTP_HOST=smtp.163.com
- ZO_SMTP_PORT=25
- ZO_SMTP_USER_NAME=njcxx0822@163.com
- ZO_SMTP_PASSWORD=NBZzx6jXtZ2G6wrB
- ZO_SMTP_FROM_EMAIL=njcxx0822@163.com
#- ZO_SMTP_ENCRYPTION=starttls
ports:
- "5080:5080"
networks:
- devops
```
  这里配置587端口会有问题使用了25端口并且去掉了安全认证。
  注意配置的邮件地址需要和用户名一致。
  打开控制台,选择右上角的设置按钮,选择模板->添加模板->Email填写标题和Body:
```json
[Alert: {alert_name}] - Severity: {alert_type}
```
```json
[
{
"labels": {
"alertname": "{alert_name}",
"stream": "{stream_name}",
"organization": "{org_name}",
"alerttype": "{alert_type}",
"severity": "critical"
},
"annotations": {
"timestamp": "{timestamp}"
}
}
]
```
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/2b747fa01dd59a6dbd3da8d2cb3579f8.png '邮箱模板.png')
  **注:这里的`{}`里面的内容需要和日志里面的字段保持一致。**
  选择地址->添加地址->Email选择刚才的模板并填写收件人。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/352d69738b7325637e5716425757ac87.png '邮箱地址.png')
  配置完模板和地址后,选择告警->添加告警例如可以选择当有level='critical'并且出现1次时触发报警并通知到指定邮箱。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/05200db676132fd43d3346099facee1b.png '邮箱告警.png')
  周期:监控数据的时间窗口范围,即每次检查告警条件时,回溯多长时间的监控数据。如果周期 = 5分钟每次检查时分析最近 5 分钟的日志/指标数据。
  频率:告警规则检查的时间间隔,即每隔多久执行一次告警条件判断。
  通知后静默:触发告警并发送通知后,系统暂时停止重复通知的时间。
  也可以设置为实时告警,即当告警发生时就立即触发,不会有周期和频率参数设置。
### 3.3 配置WebHook
  首先配置模板和WebHook地址。
  打开控制台,选择右上角的设置按钮,选择模板->添加模板->Web Hook填写Body:
```json
{
"msgtype": "markdown",
"markdown": {
"content": "alertname: {alert_name} timestamp:{timestamp}"
}
}
```
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/5c6514f9d689f55aa1bf74f75f5ea806.png 'WebHook模板.png')
  选择地址->添加地址->WebHook选择刚才的模板并填写URL。
  这里可以使用企业微信的群消息推送功能添加成功后会提供一个WebHook地址。也可以自定义一个WebHook接收地址。
  配置完模板和地址后,选择告警->添加告警选择通知到刚才的WebHook地址即可。
### 3.4 通过API获取日志信息
  通过http://ip:5080/swagger/index.html#/ 即可访问OpenobserveAPI接口。
  例如要获取日志,可以使用/api/{origin_id}/_search的接口
```cmd
curl -X 'POST' \
'http://127.0.0.1:5080/api/default/_search' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"query": {
"end_time": 1759115401962000,
"from": 0,
"size": 10,
"sql": "select * from default",
"start_time": 1759029001962000
}
}'
```
  注意这里要加上Basic auth认证并提供username和password。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/d29ddbd369e32cb7c2770464e8d0a142.png 'API查看日志.png')
## 四、自定义日志告警服务
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/74d5efdb80f506395b81988258433d9d.png '日志框架.png')
  基本原理为通过提供一个HTTP的API接口当Openobserver触发告警时调用该接口并传入当前的alert_name和timestamp然后根据timestamp和Openobserver提供的API接口去查询最近一段时间内的所有日志信息并存储到SqLite数据库中。同时也可以选择是否同步推送到企业微信和邮箱。
  最后也需要提供一个查询历史日志告警信息的接口,并可视化显示,方便运维和开发查看。
  自定义服务参考代码:[log-alert-service](https://cxxgit.iepose.cn/Cxx0822/log-alert-service)

9
docs/DevOps/index.md Normal file
View File

@@ -0,0 +1,9 @@
---
layout: doc
title: DevOps
---
# 📚 内容导航
- [OpenObserve部署和简单使用](/DevOps/OpenObserve部署和简单使用)
- [Jenkins部署和使用](/DevOps/Jenkins部署和使用)

458
docs/Web/MySQL知识点.md Normal file
View File

@@ -0,0 +1,458 @@
# MySQL知识点
## 一、基础知识
### 1.1 数据类型
#### 1.1.1 汇总
|类型|存储空间|范围|适用场景|备注|
| :-------------------------------------------------------------------------------------------------: | :-----------------: | :--------------------------: | :-----------------------: | :------------------------------------: |
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">TINYINT</span>|1字节|-128 \~ 127|状态码、年龄、布尔值||
|SMALLINT|2字节|-32,768 \~ 32,767|小范围计数、年份||
|MEDIUMINT|3字节|-8,388,608 \~ 8,388,607|中型ID、访问量统计||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">INT/INTEGER</span>|4字节|-2\^31 \~ 2\^31-1|用户ID、订单号常用||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">BIGINT</span>|8字节|-2\^63 \~ 2\^63-1|分布式ID、大数据量计数||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">DECIMAL(M,D)</span>|变长M+2字节||金融金额、精确计算|精确小数M\=总位数D\=小数位|
|FLOAT|4字节||科学测量、非精确计算|单精度浮点约6-7位有效数字|
|DOUBLE|8字节||地理坐标、高精度计算|双精度浮点约15位有效数字|
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">CHAR(M)</span>|255字符||固定长度编码、MD5哈希||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">VARCHAR(M)</span>|65,535字节||用户名、地址等变长数据||
|TINYTEXT|255字节||短标题、简介||
|TEXT|65,535字节||文章内容、评论||
|MEDIUMTEXT|16MB (2\^24-1)||博客文章、产品描述||
|LONGTEXT|4GB (2\^32-1)||电子书、大型文档||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">DATE</span>|||纯日期||
|TIME(fsp)|||可指定微秒精度(0-6)||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">DATETIME(fsp)</span>|||高精度时间记录||
|TIMESTAMP(fsp)|||自动时区转换4字节存储||
|ENUM|||性别、状态等有限选项|`gender ENUM('M','F','O')`|
|SET|||用户兴趣、文章标签|`tags SET('red','green','blue')`|
|TINYBLOB|255字节||微小二进制对象||
|BLOB|65KB||标准二进制对象||
|MEDIUMBLOB|16MB||中等二进制对象||
|LONGBLOB|4GB||超大二进制对象||
#### 1.1.2 法则
&emsp;&emsp;整数选择优先INT大数量用BIGINT布尔值用TINYINT(1)或BIT(1)
&emsp;&emsp;小数选择金融金额必须用DECIMAL非精确测量可用FLOAT/DOUBLE
&emsp;&emsp;字符串选择定长编码用CHAR变长文本用VARCHAR大文本用TEXT系列
&emsp;&emsp;时间选择日期用DATE精确时间用DATETIME自动更新用TIMESTAMP
&emsp;&emsp;特殊场景多选项用SET结构化数据用JSON
### 1.2 常用函数
#### 1.2.1 字符串函数
1. CONCAT(str1, str2, ...)​​:连接字符串
```sql
SELECT CONCAT('Hello', ' ', 'World');
```
2. LENGTH(str):返回字符串的长度(字节数)
```sql
SELECT LENGTH('Hello');
```
3. SUBSTRING(str, pos, len):从字符串 str 的 pos 位置开始,截取长度为 len 的子字符串。
```sql
SELECT SUBSTRING('Hello World', 7, 5); -- 返回 'World'
```
#### 1.2.2 日期/时间函数
1. NOW()返回当前日期和时间yyyy-MM-DD HH:mm:ss
2. CURDATE()返回当前日期不带时间部分yyyy-MM-DD
3. DATE_FORMAT(date, format):按照指定的格式返回日期值。
```sql
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s');
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d');
```
4. DATEDIFF(date1, date2):返回两个日期之间的天数差。
```sql
SELECT DATEDIFF('2024-12-31', '2024-01-01'); -- 返回364
```
5. YEAR(date) / MONTH(date) / DAY(date):分别返回日期的年、月、日部分。
6. TIME(date):从日期或时间值中提取时间部分。
7. TIMESTAMPDIFF(unit, datetime1, datetime2):返回两个日期或时间的差值,单位可以是 SECOND, MINUTE, HOUR, DAY, MONTH, YEAR 等。
8. STR_TO_DATE(str, format):根据给定的格式将字符串转换为日期。
```sql
SELECT STR_TO_DATE('01-09-2024', '%d-%m-%Y'); -- 返回 '2024-09-01'
```
#### 1.2.3 数值函数
1. ROUND(x, d):将数值 x 四舍五入到 d 位小数。
```sql
SELECT ROUND(123.4567, 2); -- 返回 123.46
```
2. FLOOR(x) / CEIL(x):返回小于或等于 x 的最大整数(向下取整)或大于或等于 x 的最小整数(向上取整)。
```sql
SELECT FLOOR(2.9); -- 返回 2
SELECT CEIL(2.1); -- 返回 3
```
#### 1.2.4 聚合函数
1. COUNT(expression):返回某列中的记录数。
2. SUM(expression):返回某列中数值的总和。
3. AVG(expression):返回某列中数值的平均值。
4. MAX(expression) / MIN(expression):返回某列的最大值或最小值。
#### 1.2.5 控制流函数
1. IF(condition, true_value, false_value):如果 condition 为真,返回 true_value否则返回 false_value。
```sql
SELECT IF(1 > 0, 'Yes', 'No'); -- 返回 'Yes'
```
2. CASE用于条件判断类似于多路选择。
```sql
SELECT
CASE
WHEN salary > 10000 THEN 'High'
WHEN salary BETWEEN 5000 AND 10000 THEN 'Medium'
ELSE 'Low'
END AS salary_range
FROM employees;
```
## 二、索引
&emsp;&emsp;索引是对数据库表中的一列或多列值进行**排序**的一种结构,使用索引可以快速访问数据库表中的特定信息。
&emsp;&emsp;**索引相当于图书上的目录,可以根据目录上的页码快速找到所需的内容,提高性能(查询速度)**
### 2.1 优缺点
优点:
* **查询速度起飞 (主要目的)** :通过索引,数据库可以**大幅减少需要扫描的数据量**,直接定位到符合条件的记录,从而显著加快数据检索速度,减少磁盘 I/O 次数。
* **保证数据唯一性**:通过创建**唯一索引 (Unique Index)** 可以确保表中的某一列或几列组合的值是独一无二的比如用户ID、邮箱等。**主键本身就是一种唯一索引**。
* **加速排序和分组**:如果查询中的 ORDER BY 或 GROUP BY 子句涉及的列建有索引,数据库往往可以直接利用索引已经排好序的特性,避免额外的排序操作,从而提升性能。
缺点:
* **创建和维护耗时**:创建索引本身需要时间,特别是对大表操作时。更重要的是,当对表中的数据进行**增、删、改 (DML操作)** 时,不仅要操作数据本身,相关的索引也必须动态更新和维护,这会**降低这些 DML 操作的执行效率**。
* **占用存储空间**:索引本质上也是一种数据结构,需要以物理文件(或内存结构)的形式存储,因此会**额外占用一定的磁盘空间**。索引越多、越大,占用的空间也就越多。
* **可能被误用或失效**:如果索引设计不当,或者查询语句写得不好,数据库优化器可能不会选择使用索引(或者选错索引),反而导致性能下降。
### 2.2 适用场景
适用场景
* **频繁作为查询条件的字段**应该创建索引
* **查询中排序的字段**创建索引将大大提高排序的速度(索引就是排序加快速查找)
* **查询中统计或者分组的字段**
不适用场景
* **频繁更新的字段不适合创建索引**,因为每次更新不单单是更新记录,还会更新索引,保存索引文件
* **表记录太少,不需要创建索引**
* **数据重复且分布平均的字段**,因此为经常查询的和经常排序的字段建立索引。注意某些数据包含大量重复数据,因此他建立索引就没有太大的效果,例如性别字段,只有男女,不适合建立索引。
### 2.3 数据结构
&emsp;&emsp;在 MySQL 中MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/91d2e40a1dae728c4061d1e1845e0ae3.png 'MySQL索引.png')
&emsp;&emsp;例如要查找id=75的用户`SELECT * FROM users WHERE id = 75`
&emsp;&emsp;查找步骤:
1. 从根节点开始75在50~100之间走中间分支
2. 到达叶子节点找到id=75的数据位置
3. 根据位置直接获取完整的用户数据
&emsp;&emsp;整个过程只需要3次IO操作而全表扫描可能需要很多次。
|特点|优势|实际效果|
| :--------------: | :------------: | :--------------------: |
|多路平衡|树的高度很低|减少磁盘访问次数|
|叶子节点连接|支持范围查询|ORDER BY、分页查询快|
|只在叶子存数据|内部节点小|更多索引数据放入内存|
### 2.4 索引类型
1. 主键索引
&emsp;&emsp;主键索引是最特殊的索引。
```sql
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
email VARCHAR(100)
);
SELECT * FROM users WHERE id = 12345;
```
2. 唯一索引
```sql
CREATE UNIQUE INDEX idx_email ON users(email);
```
&emsp;&emsp;插入重复的邮箱会报错
3. 普通索引
```sql
CREATE INDEX idx_name ON users(name);
SELECT * FROM users WHERE name = "张三";
```
4. 复合索引
&emsp;&emsp;多个字段组合的索引​
```sql
CREATE INDEX idx_name_age_city ON users(name, age, city);
SELECT * FROM users WHERE name = '张三';
SELECT * FROM users WHERE name = '张三' AND age = 25;
SELECT * FROM users WHERE name = '张三' AND age = 25 AND city = '北京';
SELECT * FROM users WHERE age = 25; -- 无法使用索引
SELECT * FROM users WHERE city = '北京'; -- 无法使用索引
```
&emsp;&emsp;复合索引的使用最左前缀原则。
&emsp;&emsp;`CREATE INDEX idx_name_age_city ON users(name, age, city);`相当于创建了3个索引`users(name)users(name, age)users(name, age, city)`​。
### 2.5 索引设计
* 为WHERE条件添加索引
```sql
SELECT * FROM orders WHERE user_id = 123;
SELECT * FROM orders WHERE status = 'paid';
SELECT * FROM orders WHERE create_time > '2024-01-01';
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_create_time ON orders(create_time);
```
* 为ORDER BY字段添加索引
```sql
SELECT * FROM articles ORDER BY create_time DESC LIMIT 10;
CREATE INDEX idx_create_time ON articles(create_time);
```
* 复合索引的顺序很关键
```sql
SELECT * FROM users WHERE city = '北京' AND age > 25 ORDER BY create_time;
-- 索引字段顺序应该是:过滤性强的字段在前
CREATE INDEX idx_city_age_create_time ON users(city, age, create_time);
```
* 限制每张表上的索引数量,建议单张表索引不超过 5 个。
* 被频繁更新的字段应该慎重建立索引,虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。 如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。
* 尽可能的考虑建立联合索引而不是单列索引。因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+ 树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
### 2.6 索引优化
1. 监控慢查询
```sql
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 超过1秒的查询记录下来
-- 查看慢查询
SHOW VARIABLES LIKE 'slow_query_log_file';
```
2. 合理使用前缀索引
```sql
-- 对于很长的字符串字段,使用前缀索引
CREATE INDEX idx_title_prefix ON articles(title(20)); -- 只索引前20个字符
```
## 三、事务
&emsp;&emsp;事务是数据库操作的基本单位,它是一组原子性的 SQL 语句,或者说是一个独立的工作单元。事务内的所有操作**要么全部成功,要么全部失败**。
### 3.1 特性
* **原子性**`Atomicity`​):原子性确保事务中的所有操作要么全部完成,要么全部不完成。如果事务执行过程中发生错误,所有已执行的操作都会回滚。
```sql
START TRANSACTION;
INSERT INTO orders (user_id, amount) VALUES (1, 100);
UPDATE inventory SET stock = stock - 1 WHERE product_id = 1;
-- 如果任何一步失败,整个事务都会回滚
COMMIT;
```
* **一致性**`Consistency`​):一致性确保数据库从一个一致的状态转换到另一个一致的状态。事务执行前后,数据库的完整性约束不会被破坏。
```sql
START TRANSACTION;
-- 确保账户余额不会出现负数
UPDATE accounts SET balance = balance - 100
WHERE id = 1 AND balance >= 100;
UPDATE accounts SET balance = balance + 100
WHERE id = 2;
COMMIT;
```
* **隔离性**`Isolation`​):隔离性确保并发执行的事务之间不会相互影响。每个事务都感觉不到其他事务的存在。
```sql
-- 事务1
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1;
-- 其他事务的修改不会影响这个查询结果
COMMIT;
-- 事务2
START TRANSACTION;
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
COMMIT;
```
* **持久性**`Durability`​):持久性确保一旦事务提交,其所做的修改就会永久保存到数据库中。
```sql
START TRANSACTION;
INSERT INTO logs (message) VALUES ('重要操作');
COMMIT;
-- 提交后,数据已经持久化到磁盘
```
### 3.2 隔离级别
#### 3.2.1 概念
* **脏读 (Dirty Read):** 事务A读取了事务B**尚未提交**的修改数据。如果事务B最终回滚事务A读到的就是无效的"脏"数据。
* **不可重复读 (Non-Repeatable Read):** 在同一个事务A中**两次读取同一行数据**得到的结果不同。这是因为在两次读取之间该行数据被另一个提交了的事务B**修改**了。
* **幻读 (Phantom Read):** 在同一个事务A中**两次执行相同的查询**(通常是范围查询 `SELECT ... WHERE ...`​),得到的结果集行数不同(出现了新的"幻影"行或原有行消失了。这是因为在两次查询之间另一个提交了的事务B**插入**了满足查询条件的新行或**删除**了原有的行。
#### 3.2.2 类型
1. 读未提交 (READ UNCOMMITTED)
* 特点: 这是最低的隔离级别,允许读取尚未提交的数据变更。
* 允许的问题:
* **脏读:** 事务可以读取其他事务尚未提交的修改。
* **不可重复读:** 可能发生。
* **幻读:** 可能发生。
* 并发性: **最高**。因为它几乎不加锁(或者锁持有时间非常短),事务之间等待最少。
* 数据一致性: **最差**。读取的数据可能是临时的、无效的(如果其他事务回滚)或中间状态。
* 使用场景: 非常罕见,通常仅在对数据准确性要求极低、需要极高吞吐量且能容忍脏数据的统计类场景(如实时大屏粗略计数)中考虑。**强烈不建议在要求数据准确性的业务中使用。**
2. 读已提交 (READ COMMITTED)
* 特点: 允许读取并发事务已经提交的数据,这是许多数据库(如 Oracle, PostgreSQL的默认隔离级别但不是 MySQL 的默认)。
* 允许的问题:
* **脏读: ❌ 避免。** 事务只能读取其他事务**已经提交**的修改。
* **不可重复读: ✔️ 可能发生。** 同一事务内多次读取同一行,结果可能不同(如果其他已提交事务修改了该行)。
* **幻读: ✔️ 可能发生。** 同一事务内多次执行相同范围查询,结果集行数可能不同(如果其他已提交事务插入/删除了满足条件的行)。
* 并发性: **较高**。避免了脏读带来的最基础问题,锁的持有时间通常比 `REPEATABLE READ` 短(行锁在语句执行后可能更快释放)。
* 数据一致性: **较好**。保证了读取的数据是已提交的、有效的。但同一事务内的多次读取结果可能不一致。
* 使用场景: 适用于大多数不需要在同一个事务内保证多次读取数据绝对一致的场景。例如,一个展示数据的列表页,每次查询都是独立的快照。
3. **可重复读 (REPEATABLE READ)**
* 特点: 确保在同一事务中多次读取同一数据得到相同的结果。这是 **MySQL 的默认事务隔离级别**。通过 **MVCC (多版本并发控制)** 实现。
* 允许的问题:
* **脏读: ❌ 避免。**
* **不可重复读: ❌ 避免。** 在同一事务内,**多次读取同一行**数据的结果保证是一致的即使其他事务已提交修改。MVCC 通过为事务提供**一致性视图 (Consistent Read View)** 来实现,该视图基于事务开始时的快照。
* **幻读: ⚠️ 理论可能,但 MySQL InnoDB 很大程度上避免。** 这是关键点!标准的 SQL 定义中,`REPEATABLE READ` 允许幻读。但是,**MySQL 的 InnoDB 存储引擎通过 MVCC 和 Next-Key Locking临键锁的组合在绝大多数情况下避免了幻读**。对于**快照读**(普通 `SELECT` 语句MVCC 保证看到的是事务开始时的快照,因此不会看到新插入的行。对于**当前读**`SELECT ... FOR UPDATE`, `SELECT ... LOCK IN SHARE MODE`, `UPDATE`, `DELETE`Next-Key Locking 会锁定扫描到的索引范围,阻止其他事务在该范围内插入,从而避免幻读。
* 并发性: **中等**。比 `READ COMMITTED` 稍低,因为锁(特别是 Next-Key Locks可能持有更长时间覆盖更大的范围索引区间
* 数据一致性: **好**。保证了事务内读取数据的稳定性(同一行可重复读),并通过机制有效防止了幻读,满足大多数应用的需求。
* **使用场景:** MySQL 的默认选择,适用于绝大多数需要保证事务内数据读取一致性的场景,如订单处理、账户管理等。是兼顾一致性和并发性的良好平衡点。
*
4. 串行化 (SERIALIZABLE)
* 特点: 最高的隔离级别。它通过强制事务**串行执行**来实现,所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。
* 允许的问题:
* **脏读: ❌ 避免。**
* **不可重复读: ❌ 避免。**
* **幻读: ❌ 避免。**
* **实现方式:** 简单理解,它会在读取的数据上自动加共享锁(`SELECT` 默认变成 `SELECT ... LOCK IN SHARE MODE`​),在写入的数据上加排他锁。这些锁会持有到事务结束。这导致事务之间几乎完全串行化,读写相互阻塞非常严重。
* 并发性: **最低**。性能开销巨大,吞吐量急剧下降。
* 数据一致性: **最好**。完全保证事务的隔离性,不会出现任何并发问题。
* 使用场景: 仅在对数据一致性要求极高,且完全不能接受任何并发副作用(如金融核心系统的某些极端操作),并且能承受极低并发性能的情况下使用。**实践中很少使用。**
### 3.3 MySQL锁
* **表级锁:** MySQL 中锁定粒度最大的一种锁全局锁除外是针对非索引字段加的锁对当前操作的整张表加锁实现简单资源消耗也比较少加锁快不会出现死锁。不过触发锁冲突的概率最高高并发下效率极低。表级锁和存储引擎无关MyISAM 和 InnoDB 引擎都支持表级锁。
* **行级锁:** MySQL 中锁定粒度最小的一种锁,是 **针对索引字段加的锁** ,只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。行级锁和存储引擎有关,是在存储引擎层面实现的。
* **共享锁S 锁)** :又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
* **排他锁X 锁)** :又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条事务加任何类型的锁(锁不兼容)。
|SQL语句|默认锁类型|说明|
| :-----------------------------: | :------------: | :----------------------------: |
|SELECT|无锁MVCC|使用快照读取,不阻塞其他事务|
|SELECT ... FOR UPDATE|排他锁(X)|阻塞其他事务修改这些行|
|SELECT ... LOCK IN SHARE MODE|共享锁(S)|允许其他事务读取但阻塞修改|
|INSERT|排他锁(X)|自动获取|
|UPDATE|排他锁(X)|自动获取|
|DELETE|排他锁(X)|自动获取|
## 四、日志
&emsp;&emsp;MySQL 的三大核心日志系统是保证数据一致性、实现故障恢复和提供复制功能的关键组件。这三大日志分别是:​​二进制日志(binlog)​​、​​错误日志(error log) 和 ​​重做日志(redo log)​​。
### 4.1 二进制日志Binary Log简称 binlog
#### 4.1.1 概念
&emsp;&emsp;二进制日志​​是 MySQL ​​记录所有修改数据或可能修改数据的语句(或数据变更)的日志文件​​。它记录了数据库执行的​​更改操作​​(如 INSERT、UPDATE、DELETE 等 DML 操作,以及 CREATE、ALTER、DROP 等 DDL 操作),但**不记录 SELECT 这类不修改数据的查询操作**。
#### 4.1.2 作用
1. 主从复制Replication在主从架构中主库将 binlog 发送给从库,从库通过读取并重放 binlog 来保持与主库的数据同步。
2. 数据恢复Point-in-Time Recovery通过备份 + binlog 可以恢复到某个具体时间点。
3. ​​审计​​:可以追踪数据库的所有变更操作。
### 4.2 重做日志Redo Log
#### 4.2.1 概念
&emsp;&emsp;重做日志​​是 InnoDB 存储引擎特有的日志,它记录的是 ​​“物理级别” 上的页修改信息​​,主要用于 崩溃恢复Crash Recovery
&emsp;&emsp;InnoDB 使用 WALWrite-Ahead Logging预写日志技术即 ​​事务提交时,先写 redo log再写磁盘数据页从而保证即使系统崩溃也能通过 redo log 恢复数据。
#### 4.2.2 作用
1. 实现事务的持久性Durability确保事务提交后即使发生宕机数据也不会丢失。
2. ​​提高写入性能​​:数据不是每次修改都直接写磁盘,而是先写 redo log顺序写速度快后续再异步刷盘。
3. ​​支持 crash-safeMySQL 宕机重启后,可通过 redo log 恢复未刷盘的数据。
### 4.3 回滚日志Undo Log
#### 4.3.1 概念
&emsp;&emsp;回滚日志​​也是 InnoDB 引擎特有​​ 的日志,它记录的是 ​​数据被修改前的原始值​​
#### 4.3.2 作用
1. 支持事务回滚​​:如果事务执行失败或调用了 ROLLBACK可以通过 undo log 将数据恢复到修改之前的状态。
2. 实现 MVCC多版本并发控制在读已提交RC、可重复读RR隔离级别下undo log 用于提供历史版本数据,使得不同事务能看到不同的数据快照,而不需要加锁。
### 4.4 和事务、锁之间的关系
&emsp;&emsp;例如当执行一条SQL语句时事务、锁、日志等之间的关系
1. 发起一个事务
2. 执行一系列的DML操作如 INSERT/UPDATE/DELETE
&emsp;&emsp;**涉及 ​​锁​​:对操作的数据行或表加锁,防止其他事务干扰。**
&emsp;&emsp;**涉及 ​​隔离级别​​:决定其他事务是否能“看到”你未提交的数据。**
&emsp;&emsp;**涉及 undo log如果事务回滚可以根据 undo log 恢复旧值。**
3. 事务提交commit 或 回滚rollback
&emsp;&emsp;**redo log保证即使宕机已提交事务的修改也不丢失持久性。**
&emsp;&emsp;**binlog记录数据变更用于主从复制与时间点恢复。**
&emsp;&emsp;**undo log用于实现事务回滚、MVCC。**
4. **背后有日志系统默默记录一切,锁系统保障并发安全,隔离级别定义了“你能看到啥”。​**
#### 4.4.1 相互关系
1. 事务是锁的使用者,锁是事务实现隔离性的手段。​当一个事务对某行数据执行 UPDATE/DELETE 操作时为了防止其他事务同时修改相同数据InnoDB 会自动对该行或索引加 排他锁X锁。如果事务只是读取数据根据隔离级别可能会加 共享锁S锁 或使用 MVCC不加锁
2. 隔离级别定义了事务之间的可见性规则,是事务“隔离性”的具体体现。​事务的隔离性是通过锁 + MVCC多版本并发控制依赖 undo log+ 隔离级别共同实现的。​
3. 日志是事务实现 持久性、崩溃恢复、回滚 等能力的基石。redo log重做日志保障事务的持久性Durability事务提交时先将数据页的变更记录到 redo log顺序写高性能随后再异步刷盘到磁盘数据页。即使系统崩溃重启后也能通过 redo log 恢复已提交但尚未刷盘的数据。undo log回滚日志支持事务回滚 和 MVCC。事务修改数据前会先把原始数据保存到 undo log如果事务回滚可以用它恢复旧值。同时undo log 也是 MVCC多版本控制实现的基础用于提供历史版本数据。binlog二进制日志用于主从复制、时间点恢复。虽然 binlog 是 Server 层的日志,不属于 InnoDB 事务引擎的一部分但在事务提交时binlog 与 redo log 通过两阶段提交2PC保证一致性
4. 隔离级别决定了锁的粒度和行为,锁是隔离级别的底层实现机制之一。​在 READ UNCOMMITTED 下,一般不加锁(脏读允许)。在 READ COMMITTED 和 REPEATABLE READInnoDB 通过 ​​行锁 + MVCC 实现隔离,不一定非得阻塞其他事务。在 SERIALIZABLE 下,会自动为读操作也加上共享锁,相当于所有操作串行执行,隔离性最强,但并发性能最低。
5. 锁保障并发安全,日志保障操作可恢复。两者从不同维度确保数据库的正确性。​锁​​:是在运行时控制谁可以访问哪些数据,是 ​​并发控制​​ 的手段。​​日志​​:是在磁盘上记录操作过程,是 ​​故障恢复 & 事务一致性​​ 的手段。
6. 隔离级别定义了事务间数据的可见性,而日志(尤其是 undo log 和 binlog为这种“可见性”提供了实现基础。undo log 是实现 MVCC多版本并发控制 的基础,而 MVCC 是 REPEATABLE READ 等隔离级别的关键。binlog 虽不直接影响隔离性,但它记录了事务的变更历史,是构建主从环境、实现数据恢复的基础。
## 五、优化
### 5.1 Explain 执行计划
&emsp;&emsp;`EXPLAIN`​是 MySQL 自带的一个诊断工具,它可以模拟 MySQL 查询优化器的执行过程,对`SELECT`​语句(在 MySQL 8.0 及以上版本,也支持对`UPDATE`​、`DELETE`​等语句使用)进行分析,并输出该语句的执行计划。
#### 5.1.1 基本用法
&emsp;&emsp;在select语句前面加上EXPLAIN关键字即可例如`EXPLAIN SELECT * FROM users WHERE age > 25;
`
#### 5.1.2 输出列说明
| 列名 | 说明 | 示例 |
| :-: | :-: | :-: |
| id | 查询标识符 | 相同 id 表示同组查询,执行顺序从上到下;不同 id 值越大优先级越高 |
| select_type | 查询类型 | SIMPLE无子查询、PRIMARY外层查询、SUBQUERY子查询 |
| table | 访问的表名 | |
| partitions | 匹配的分区 | |
| **type** | 访问类型 | system > const > eq_ref > ref > range > index > ALL(性能核心指标,从优到劣排序) |
| possible_keys | 可能使用的索引 | |
| **key** | 实际使用的索引 | |
| key_len | 索引使用的字节数 | |
| ref | 索引匹配的列或常量 | |
| **rows** | 预估扫描行数 | 越小越好 |
| filtered | 存储引擎返回数据后在 server 层过滤的比例 | |
| **Extra** | 额外执行信息 | |
#### 5.1.3 重要指标Type
| 类型 | 描述 | 性能 | 示例 |
| - | - | - | - |
| system | 系统表,仅一行 | 最优 | MyISAM 引擎的空表 |
| const | 主键/唯一索引的常量查询 | 极优 | WHERE id = 1 |
| eq_ref | JOIN 时主键/唯一索引关联 | 优 | JOIN ... ON t1.pk = t2.pk |
| ref | 非唯一索引的等值查询 | 良 | WHERE index_col = 10 |
| fulltext | 全文索引 | 中 | MATCH(...) AGAINST(...) |
| ref_or_null | ref + NULL 值搜索 | 中 | WHERE col = 10 OR col IS NULL |
| index_merge | 索引合并优化 | 中 | 多个索引条件组合 |
| unique_subquery | 唯一索引子查询 | 中 | value IN (SELECT pk FROM ...) |
| index_subquery | 非唯一索引子查询 | 中下 | value IN (SELECT index_col FROM ...) |
| range |索引范围扫描 | 中下 | WHERE id > 10 |
| index | 全索引扫描 | 差 | SELECT indexed_col FROM table |
| ALL | 全表扫描 | 最差 | 无索引查询|
#### 5.1.4 重要指标Extra
| 值 | 含义 | 优化建议 |
| - | - | - |
| Using index | 覆盖索引(无需回表) | 优,保持 |
| Using where | Server 层过滤数据 | 检查索引使用 |
| Using temporary | 使用临时表 | 优化 GROUP BY/ORDER BY |
| Using filesort | 额外排序操作 | 为排序字段加索引 |
| Select tables optimized away | 使用聚合函数优化 | 优 |
| Using index condition | 索引条件下推ICP | MySQL 5.6+ 优化特性 |
| Using join buffer | 使用连接缓冲区 | 增大 join_buffer_size |
| Impossible WHERE | WHERE 条件永不成立 | 查询逻辑错误 |
| Distinct | 优化 DISTINCT 操作 | |
## 六、执行过程
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/8ddaa246aef63b6be371487ad7159e40.png '执行过程.png')
&emsp;&emsp;MySQL架构分为两层Service层和存储引擎层。
&emsp;&emsp;Server 层负责建立连接、分析和执行 SQL。存储引擎层负责数据的存储和提取。
1. 连接器:建立连接、管理连接、校验用户身份。
2. 查询缓存MySQL8.0已删除)
3. 解析SQL通过解析器对SQL查询语句进行词法分析、语法分析然后构建语法树方便后续模块读取表名、字段和语句类型等。
4. 执行SQL分为预处理阶段、优化阶段和执行阶段。

8
docs/Web/index.md Normal file
View File

@@ -0,0 +1,8 @@
---
layout: doc
title: Web
---
# 📚 内容导航
- [MySQL知识点](/Web/MySQL知识点)

29
docs/index.md Normal file
View File

@@ -0,0 +1,29 @@
---
layout: home
hero:
name: "Cxx0822知识库"
text: "技术成长之路"
tagline: 记录学习笔记 | 分享开发经验 | 构建知识体系
# actions:
# - theme: brand
# text: 开始探索
# link: /web/
# - theme: alt
# text: GitHub
# link: https://github.com/你的用户名
features:
- title: 📚 系统学习
details: 从基础到进阶,系统整理开发知识体系,帮助构建完整的技能树
- title: 💻 实战笔记
details: 记录项目开发中的实际问题与解决方案,包含代码示例和最佳实践
- title: 🚀 技术分享
details: 分享新技术探索、性能优化、架构设计等深度技术内容
- title: 📝 持续更新
details: 知识库持续维护更新,跟随技术发展趋势,保持内容的新鲜度
- title: 🔍 快速检索
details: 清晰的分类导航和搜索功能,方便快速定位所需的技术知识点
- title: 🌱 成长记录
details: 记录个人技术成长的每一个阶段,见证从新手到专家的蜕变过程
---