SSE 原理

1. 学习主题 我要学习的内容: SSE 原理 我为什么要学它: (写清楚用途、场景、目标,比如面试、项目、论文、考试) ...

二月 18, 2026

计算机组成原理

全局框架 - 全局框架 - 系统层次 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218105815855.png,424,537) - 核心矛盾:速度差 - CPU 运算快、内存慢、外设更慢 - 系统用层次结构(寄存器/Cache/内存/磁盘)和并行性(流水线/乱序/多核/DMA)来隐藏或者摊薄延迟 - 指令在 CPU 里按“取值-译码-执行-访存-写回“的数据通道运行 - 为了加速,做流水线把这 5 步重叠 - 内存访问通过 Cache 利用局部性降低平均延迟 - 虚拟内存通过 MMU/TLB 把程序看到的地址映射到物理内存 - I/O 慢是由于设备与总线带宽/延迟远弱于 CPU,因此依赖中断、缓冲与 DMA 来减少 CPU 参与 指令系统与数据表示 - 指令系统与数据表示 - 整数与位运算的数据表示 - 位、字节、字 - bit:0/1 - Byte:8bit - Word(字):CPU 自然处理的宽度,与寄存器/ALU/地址宽度相关 - 为什么 byte 是最小编址单位:大多数现代机器按照字节编址,方便表示字符/结构体/网络数据等 - 原码/反码/补码 - 原码 - 表示方法:最高位符号位,剩下是绝对值 - 问题: - 有 +0 和 -0 两种零 - 加减法需要额外处理符号,硬件复杂 - 反码 - 正数同原码 - 负数:对正数位取反 - 问题 - 仍然有 +0 和 -0 - 加法需要回卷进位 - 补码 - 正数:与无符号相同 - 负数:按位取反 + 1 - 关键性质: - 只有一个 0(没有 -0) - 加减法统一为同一套加法器:a-b=a+(~b+1) - 符号扩展简单:高位补符号位即可 - 范围不对称 [-2^{n-1},2^{n-1}-1] - 快速转换 - 补码->十进制 - 最高位 0:按无符号直接算 - 最高位 1:取反 +1 得到绝对值,再加负号 - 十进制->补码 - 正数:直接写二进制,左侧补 0 - 负数:先写绝对值二进制 -> 取反+1 - 有符号/无符号加法与溢出判断 - 无符号溢出(Unsigned overflow) - 本质:结果超出 [0, 2^n-1] - 判定方法:看进位 Carry-out - 有符号溢出 - 本质:结果超出 [-2^{n-1},2^{n-1}-1] - 判断口径统一 - 同号相加,异号结果 => 溢出 - 异号相加不会溢出 - 等价位级判定 - 符号位的进位与最高位进位输出不同=>溢出 - 字节序:大端 vs 小端 - 对 32-bit 只 0x12 34 56 78 高位在左 - 大端 - 低地址存高字节 - 内存从低地址到高地址 12 34 56 78 - 直觉:与人类书写一致 - 小端 - 低地址存低字节 - 内存从低地址到高地址:78 56 34 12 - 直觉:低地址就是低位,做逐字节扩展/截断更自然 - x86 等主流架构长期使用小端 - 对齐与 Padding - 对齐 - 要求:某类型对象的地址必须是某个中的倍数(4/8) - 为什么要对齐 - 硬件访问效率:CPU/总线按照字/双字读写;未对齐可能需要两次内存读写再拼接 - Cache line 与内存访问粒度:对齐能减少跨行/跨 cache line 的概率 - Padding(填充) - 为了满足对齐,编译器会在结构体字段之间或末尾插入空字节 - 结构体大小通常是其最大对齐需求的倍数 - 浮点数(IEEE 754) 与误差本质 - IEEE 754 基本格式 - 浮点数表示 - S(符号位):0 正 1 负 - E(指数/阶码):带拍支 bias - F(尾数/小数部分):有效数 significand 的小数部分 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218112914998.png,218,39) - 单精度 float(32-bit) - S: 1 位 - E: 8 位,bias = 127 - F: 23 位 - 双精度 double(64-bit) - S:1 位 - E: 11 位,bias=1023 - F: 52 位 - 规格化、非规格、0、INF、NaN - 规格化 - 指数 E 既不是全 0 也不是全 1 - 有隐含的 leading 1:1.F - 非规格化 - 指数 E 全 0,尾数 F 非 0 - 没有隐含 1:有效数是 0.F - 目的:渐进下溢,让非常接近 0 的数仍然能表示 - 0 - E 全 0,F 全 0 - 有 +0 和 -0 - Inf 无穷 - E 全 0,F 全 0 - 有 +0 和 -0 - NaN(非数) - E 全 1,F 非 0 - 来自非法操作:0/0 - NaN 会传播 - 0.1 + 0.2 != 0.3 - 十进制小数在二进制中往往是无线不循环小数 - 0.1 的二进制是 0.0001100111 - float/double 只能保存有限位尾数 - 存储时发生舍入,计算时再舍入,误差叠加 - 比较时用 ==,就可能不相等 CPU 执行与控制 - CPU 执行与控制 - 单周期 Datapath - 必备组件 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218153017482.png,429,229) - PC 当前指令地址寄存器 - Instruction Memory/I-Cache:根据 PC 取指令 - Register File (寄存器堆):两读一写(rs1、rs2 读;rd 写) - Immediate Generator(立即数生成/符号扩展) - ALU:算数逻辑/比较/地址计算 - Data Memory/D-Cache:load/store 访问 - Adders: - PC + 4 - PC + imm (分支目标) - 关键 MUX(多路选择器) - ALUSrc:ALU 第二操作数是来自 rs2 还是 imm - MemToReg:写回 rd 的数据来自 ALU 还是内存 - PCSrc:下一 PC 来自 PC+4 还是分支目标 - Control Unit(控制器):根据 opcode/funct 产生控制信号 - datapath = 一堆功能模块 + 多根线 + MUX 选路 - 四条指令在 datapath 里怎么走 - R 型算数:add rd, rs1, rs2 - 目标:rd = rs1 + rs2 - 数据流 - PC -> 取指令 - 寄存器堆读 rs1、rs2 - ALU 计算 ALUOut = rs1 + rs2 - 写回:rd <- ALUOut - PC 更新:PC <- PC + 4 - 关键控制 - RegWrite = 1 写寄存器 - ALUSrc = 0 第二操作数来自 rs2 - MemRead = 0, MemWrite = 0 - MemToReg = 0(写回来自 ALU) - Branch = 0(不分支) - ALUOp = R-type(由 funct 决定是 add/sub/and/or) - 访存读:lw rd, imm(rs1) - 目标:rd = Mem[rs1+imm] - 数据流 - 取指 - 读寄存器 rs1 (base),rs2 通常也读但不用 - ALU 计算有效地址:ALUOut = rs1 + imm - 数据内存读:MemOut = Mem[ALUOut] - 写回:rd <- MemOut - PC <- PC + 4 - 访存写:sw rs2, imm(rs1) - 目标:Mem[rs1+imm] = rs2 - 数据流 - 取指 - 读 rs1(base) 和 rs2(要写入的数据) - ALU 算地址:ALUOut = rs1 + imm - 数据内存写:Mem[ALUOut] <- rs2 - PC <- PC + 4 - 条件分支:beq rs1, rs2, imm - 目标:如果 rs1 == rs2,则 PC <- PC + imm;否则 PC <- PC + 4 - 数据流 - 取指 - 读 rs1,rs2 - ALU 做比较:Zero = (rs1-rs2==0) - 同时计算目标分支:Target = PC + imm - PC 选择 - if Branch & Zero:PC <- Target - else: PC <- PC + 4 - 单周期 vs 多周期 - 单周期 - 每条指令都在一个周期完成 - 周期必须长到容纳最慢指令路径 - 结果:简单但浪费,端指令也被迫跑长周期 - 多周期 - 把一条指令拆分成多个步骤,每一步一个周期 - 不同指令用不同步骤数量 - 可以复用硬件 - 性能 - 性能公式 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218153851413.png,354,137) - IC:执行的指令条数 - CPI:平均每条指令需要多少周期 - Cycle Time:每周期多长时间 (=1/主频) - Amdahl 定律(优化上限) - 如果系统中某部分占比位 f,该部分加速 S 倍,总加速比 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218154107114.png,227,64) - IPC、吞吐、延迟 - 延迟:完成单个任务/单条指令/一次请求需要的时间 - 吞吐:单位时间完成多少任务 - IPC:每周期完成多少条指令 - 吞吐 = IPC x 频率 流水线与冒险 - 流水线与冒险 - 五级流水线核心内容 - 五段功能定义 - IF:取值;PC->I-Cache;计算 PC+4 - ID:译码;读寄存器;生成立即数 - EX:ALU 运算;地址计算;分支比较 - MEM:访问 D-Cache - WB:写回寄存器 rd - 流水线寄存器 - 为了让每段每周期独立推进,段与段之间插入寄存器 - IF/ID、ID/EX、EX/MEM、MEM/WB 它们保存上一段的输出给下一段用(包含数据与控制信号) - 理想吞吐与延迟 - 非流水:一条指令用 5 个周期,下一条等它结束 - 流水:启动后每周期完成 1 条 - 延迟:单条仍然=5 个阶段 - 吞吐:理想从 1/5 提升到 1 - 流水线的代价 - 需要通多寄存器与控制逻辑 - 分支错预测要清刷流水线 - 结构冲突要加资源或停顿 - 结构冒险 - 定义 - 同一周期内两个阶段需要同一个硬件资源 - 例子 - 单端口存储器:IF 要取值,MEM 要访存 - 同一周期冲突 -> 必须 stall 或改造硬件 - 解决方案 - 分离 I-Cache/D-Cache - 多端口存储器 - 数据冒险 - 三种依赖 - RAW(Read After Write)真依赖:后读依赖前写,最关键 - WAR(Write After Read)反依赖:后写不能早于前读 - WAW(Write After Write)输出依赖:两条写同一目的寄存器 - 为什么会 RAW:写回晚,读取早 - 读寄存器在 ID - 写回寄存器在 WB - Fowwarding (旁路/转发)机制 - 不等写回到寄存器堆,直接把产生的结果从后端绕回到前段使用 - 转发路径 - EX/MEM -> ID/EX(给下一条的 EX 用) - MEM/WB -> ID/EX (给下一条或下下条的 EX 用) - Stall(插泡)与 load-use hazard - load-use 典型序列 - lw r1, 0(r2) - add r3, r1, r4 - 原因 - lw 的数据到 MEM 末才可用 - add 需要在 EX 用到 r1 - 时间上赶不上,即使有 forwarding 也不够快 - 结论:需要插入一个 bubble - 实现 - 冻结 PC 和 IF/ID - 往 ID/EX 注入一个 NOP (控制信号清零)形成 bubble - 控制冒险 - 定义:PC 的下一值不确定:到底走 PC+4 还是分支目标 - 分支决定在哪里做决定 penalty - 如果分支在 EX 才能确定 - 在分支结果出来之前,流水线已经取了后续若干条 - 一旦发现分支 taken,就要把错路径指令 flush 掉 - 基础策略 - stall until branch resolved:等分支算出来再取后续 - predict not taken:默认不跳,先取顺序;若实际 taken -> flush - 动态分支预测:用分支历史表、2-bit 饱和计数器等降低错预测 - BTB:预测 taken 时快速给出目标地址 - 延迟槽 - penalty - 分支罚时:错预测或 taken 导致需要清空的阶段数 存储体系 - 存储体系 - 核心矛盾 - CPU 周期是纳秒级,DRAM 访问通常是几十到上百纳秒级,差一个数量级 - 解决思路:分层存储 + 局部性 - Cache 用更贵更快的 SRAM 缓存最近用过的数据块,依赖时间/空间局部性降低平均访存时间 - Cache 基础概念 - Cache line (缓存行) - Cache 以“行/块“位单位搬运数据 - CPU 读写某地址时,实际会把该地址所在的整条 cache line 搬到 Cache。 - 推论:顺序访问数组通常很友好;跨行访问容易 miss - Hit/Miss 与三大指标 - Hit time:命中时访问延迟 - Miss rate:未命中比例 - Miss penalty:未命中后去下一级/内存取回的代价 - 平均访存时间 AMAT - AMAT = Hit Time + Miss Rate * Miss Penalty - MISS 分类 - Compulsory miss(冷启动):第一次访问必 miss - Capacity miss(容量):工作集超过 Cache 容量 - Conflict miss(冲突):映射/组相联导致不同块争同一行 - 映射方式:直接映射/全相联/组相联 - 前置数据 - Cache 总容量 (不含tag/元数据) = C bytes - Cache line 大小 = B bytes - 总行数 (lines) = L = C/B - 相联度 (ways) = A - 组数 (sets) = S = L/A - 直接映射 - 每个内存块只能映射到唯一一个 cache 行 - 优点:硬件简单、命中快 - 缺点:冲突 miss 多 - 全相联 - 任意内存块可放任意行 - 优点:几乎无冲突 miss - 缺点:需要对所有行比较 tag(实现复杂、命中更慢、耗能更高) - 组相联 - 内存块映射到某个 set,但可以放在 set 内任一 way。 - 折中方案 - 地址拆分 - 对于字节编址系统,地址通常拆分为:tag+index+block offset - offset 块内偏移 - 表示在一条 cache line 內的字节位置 - 位数 offset bits = log2(B) - index 组索引 - 选择 set - index bits = log2(S) - tag 标记 - 用于判断该 set 中某一 way 是否是想要的内存块 - tagbits = Address bits - index bits - offset bits - 替换策略 - LRU:替换最久没用的行 - Pseudo-LRU(伪 LRU):用更少 bit 近似 LRU - FIFO:先进先出 - 写策略 - write-throught(写穿) - 写命中:同时写 Cache 和下一级 - 优点:下一级始终最新,实现简单 - 缺点:写流量大 - write-back(写回) - 写命中:只写 Cache,把该行标记为 dirty - 该行被替换时才写回下一级 - 优点:显著减少写带宽压力 - 缺点:一致性更复杂(多核) - 写不命中 write miss 的两种策略 - write-allocate(写分配):先把整条 line 读入 Cache,再在 Cache 上写,与 write-back 搭配 - no-write-allocate(不写分配):不把 line 加入 Cache,直接写下一级,常与 write-throught 搭配 - 多级 Cache 的 AMAT - L1/L2 两级 - AMAT=HitTime_L1​+MissRate_L1​×(HitTime_L2​+MissRate_L2​×MissPenalty_L2​) - 多核:一致性 vs 一致性协议 - Cache Coherence(一致性) - 目标:对于同一内存地址 X,各核的 cache 中 X 的副本要看起来像一个指在变化,不能读各自的旧值 - 解决办法:一致性协议(MESI)保证写入能让其他核的副本失效/更新 - Memory Consistency(一致性模型,针对不同地址的顺序) - 目标:规定不同核对不同地址的读写在时间顺序上允许怎么样被观察到 - 是内存模型讨论的问题,涉及重排序与屏障。 - Coherence 解决同一地址副本一致,Consistency 解决不同地址操作的可见顺序,前者靠协议,后者靠内存模型和 fence。 - MESI 协议 - False Sharing - 定义 - 不同核访问的是不同变量,但是这些变量恰好落在同一条 cache line 里 - 其中一个核写它自己的变量,会把整条 line 的其他核副本失效,导致另一核反复 miss - 性能问题 - 引发 invalidation(整条 line 粒度) - 两个核交替写同一条 line 上不同字节 - line 在核之间反复迁移,带来大量一致性流量 虚拟内存 - 虚拟内存 - 要解决的问题 - 抽象:每个进程看到连续的虚拟内存空间(VA) - 保护:权限控制(R/W/X,用户态/内核态) - 共享:共享库/共享内存/页共享 - 扩展:按需分页 + 换入换出 - 页 - 虚拟页:进程看到的虚拟地址空间被切分成的固定大小块 - 物理页框:正式物理内存被切成同样大小的块 - 二者的大小相同,比如常见 4KB - 虚拟内存用虚拟页编号,物理内存用页框编号,页表负责把虚拟页号 -> 物理页框号 - 地址和页对应 - 页大小是 4KB - 4KB = 4096 字节 = 2^12 - 一个地址的低 12 位代表页内偏移 - 高位表示虚拟页号(VPN) - 地址转换链路 - VA -> TLB -> page table -> PTE -> PA -> Cache/Memory - 术语解释 - VA:进程发出的地址 - PA:真实内存地址 - VPN:虚拟页号 - VPO:页内偏移 - PPN:物理页框号 - PTE:页表项,记录 VPN -> PPN 的映射与权限等元数据 - TLB:页表项的高速缓存 - 页大小与地址拆分 - 页大小是 PageSize = 2^k 字节 - offsetBits = k - VPNBits = AddrBits - k - 转换过程 - CPU 产生 VA - MMU 用 VA 的 VPN 去查 TLB - TLB hit:直接得到 PPN + 权限 - 物理地址 PA = (PPN << offsetBits) | Offset - TLB miss:用 VPN 去查页表 - 拿到 PTE - 有效 -> 填入 TLB -> 形成 PA -> 继续访问 - 无效 -> 触发异常 - 页表结构 - PTE 包含什么 - PPN:物理页框号 - Present/Valid:是否在内存 - R/W/X:读写执行权限 - U/S:用户/内核权限 - Accessed/Referenced(A):是否被访问过 - Dirty(D):是否被写过 - 为什么需要多级页表 - 单级页表 - VA 空间大,VPN 组合数巨大 - 每个进程都有一张覆盖整个虚拟空间的大表 - 多级页表做法 - 把 VPN 再拆分成多端:VPN1/VPN2/.../VPNn - 顶层页表只在需要时为某个范围分配下一级页表 - 没用到的地址区间不分配页表 -> 按需分配页表 - 多级页表的 Page Walk - 先用 VPN 的高段索引顶层页表,取到二级页表的地址 - 再用下一段索引二级页表页 - 最终找到叶子 PTE 得到 PPN - 代价:要读多次内存 - TLB - 没有 TLB - 每次 load/store 都要走 page walk,可能是 4/5 级 - 意味着每次访问内存前都要额外多次访存 - TLB 的本质 - 缓存最近用过的 VPN -> PPN + 权限 - 命中时地址翻译几乎是常数空间 - 缺页异常 - 典型原因 - Demand paging(按需分页):页还没加载到内存 - Swap out:页被换出到磁盘 - 权限违规:写只读页 - Copy-on-write:写共享只读页 - 缺页处理流程 - CPU 访问 VA,发现 PTE present = 0 或者权限不符合 - 触发异常,陷入内核(保存上下文,切换到内核栈) - 内核 fault handler 判断 fault 类型 - 是合法的按需页/非法地址 - 合法 - 找到磁盘位置 - 分配物理页框 - 发起 IO 把页读入内存 - 更新页表 PTE - 更新 TLB - 恢复进程执行 - 页面置换 - 当需要一个物理页框但是没有空闲页时,OS 必须选择一个牺牲页换出 - LRU - Clock/Second-Chance(近似 LRU) - 维护环形指针 - 每个页有 Accessed/Referenced 位 - 扫描时 - 若 A=0:淘汰 - 若 A=1:把 A 清 0,给第二次机会,指针继续走 - 共享与写时复制(COW) - 为什么需要 COW - fork 的语义:子进程得到父进程内存的副本 - 如果把所有页都复制一遍,成本极高 - COW - fork 后,父子进程的页表都指向同一批物理页框 - 把这些页的 PTE 权限改为只读 - 当父或子尝试写入某页 - 写入触发 page fault - 内核分配新物理页 - 复制旧页内容到新页 - 修改该进程页表让它指向新页,恢复写权限 - 另一个进程仍然指向旧页 IO 总线 - IO 总线 - IO 本质上慢 - 设备物理特性:SSD/网卡/磁盘的延迟远远高于 CPU - 路径长:系统调用、内核态切换、驱动、协议栈、队列管理 - 数据搬运:拷贝次数多、缓存一致性/同步开销高 - I/O 的基本硬件/软件组件 - 设备、控制器、驱动 - 设备:网络、磁盘、USB 设备等 - 控制器:设备侧的执行单元,负责 DMA、队列、寄存器接口等 - 驱动:内核中的软件,负责配置设备、提交请求、处理中断、管理队列 - 数据通路常见参与者 - CPU 核心 - Cache - 内存 - I/O 互联 (PCIe) - 设备控制器 - 设备介质 - 轮询 vs 中断 - 轮询 - 做法:CPU 不断读取设备状态寄存器 - 优点:实现简单、延迟可控 - 缺点:浪费 CPU、高并发/多设备会严重占用 CPU - 中断 - 设备完成后发中断信号,CPU 暂停当前执行,进入中断处理程序(ISR) - 优点: - CPU 不需要忙等->提升整体吞吐 - 缺点: - 有中断开销:恢复/保存上下文、切换栈、处理 ISR - 中断频繁造成抖动 - DMA - 不用 DMA 的程序搬运 - CPU 循环从设备寄存器读数据,再写入内存 - 缺点:CPU 被数据搬运绑死,带宽高、功耗高 - DMA 的本质 - DMA 让设备/控制器直接读写内存,CPU 只做设置与收尾 - CPU 配置 DMA 描述符:内存地址、长度、方向、队列位置 - DMA 引擎通过总线把数据直接写入/读出 DRAM - 完成后用中断通知 CPU(或写状态寄存器) - DMA 为什么更快 - 释放 CPU 周期 - DMA 更贴近总线/设备,能更高效做 burst 传输 - 配合 ring buffer/descriptor queue 能批量提交 - 内存映射 I/O 与端口 I/O - MMIO - 设备寄存器被映射到某段物理地址空间 - CPU 用普通的 load/store 指令读写这些地址,就等价于读写设备寄存器 - Port-mapped I/O - 使用专门的 IN/OUT 指令访问 I/O 端口空间 - 现代系统多以 MMIO 为主 - 零拷贝 - 传统读文件再发网络的拷贝路径 - read():磁盘->内核页缓存->拷贝到用户缓冲区 - write():用户缓冲区->再拷贝回内核 socket buffer -> 网卡 DMA 发走 - 至少需要两次 CPU 拷贝(内核<->用户,用户<->内核) - sendfile 思路 - 直接让内核把 page cache 中的文件页挂到 socket 发送队列 - 避免拷贝到用户态再拷回内核态 - 网卡通过 DMA 从内核缓冲直接发 - mmap 思路 - mmap 把文件映射进进程地址空间 - 应用像访问内存一样访问文件内容,缺页时 OS 按需把页载入 page cache - 适合随机访问,但是直接转发场景 sendfile 更适合

二月 18, 2026

计算机网络

网络分层 + 端到端路径 - 网络分层 + 端到端路径 - 分层模型 - 链路层 (LINK) - 负责:同一链路/局域网内把 IP 包送到下一跳 - 典型概念:以太网、WIFI、ARP、MAC、MTU - 网络层 (IP) - 负责:跨网络把包从源 IP 路由到目的 IP - 典型概念:IP 地址、路由表、NAT、ICMP、分片 - 传输层 (Transport) - 负责:端到端进程通信,提供端口与可靠性 - 应用层 (Application) - 负责:定义应用语义 - 端到端路径(一次 HTTPS URL 发生了什么) - URL 解析与策略决策(应用层前置) - 浏览器先拆 URL: - schema:https -> 需要 TLS - host:example.com -> 需要 DNS - port:默认 443 - path:/index.html - 是否可用缓存 - 是否复用已有连接 - DNS 解析:把域名变成 IP - 目标:得到服务器的 IP (A=IPv4/AAAA=IPv6) - 典型流程 - 浏览器/OS 先查本地缓存 - 没命中 -> 向递归解析器发起查询 - 递归解析器询问:根 -> TLD -> 权威 DNS - 返回 IP + TTL - TTL 决定缓存时长 - 同一域名可能返回多个 IP:简单的负载均衡,但是不可控 - TCP 连接(传输层) - 目标:建立一条到服务器IP:443的 TCP 连接 - 典型流程 - 客户端选一个临时端口作为源端口 - 形成 socket 四元组 - 三次握手完成后,连接进入 ESTABLISHED - 连接的唯一标识符是四元组 - NAT 环境下:srcIP 可能是私网 IP,但是公网会被 NAT 改写 - RTT 大时,建连成本高:三次握手至少消耗 1 个 RTT - NAT:内网 IP 也能访问公网(网络层/链路层交叉) - 大多数客户端在 NAT - 内网 192.168.x.x:ramdomPort - 出口 NAT 把它映射成:公网 IP:另一个 Port - NAT 设备维护一张映射表(内网四元组<->公网四元组) - 映射有超时:长时间无数据可能被回收 - 并发连接太多可能端口耗尽 - TLS 握手:在 TCP 上建立安全通道 - 目标 - 验证服务器身份 - 协商密钥 - 后续数据用对称加密 + 完整性保护 - TLS 发生在 TCP 建立之后、HTTP 之前 - 证书用于“证明对方是它声称的域名“ - 发送 HTTP 请求并接受响应 - HTTP 内容 - 请求行:GET /index.html - 头:Host、User-Agent、Accept、Cookie - 体:GET 通常无,POST 常见 - 服务器返回 - 状态码:200/301/404/500 - 头:Content-Type、Content-Length、Set-Cookie - 体:HTML/JSON/图片等 - 连接复用与关闭:不是每次都重新握手 - HTTP/1.1 默认倾向长连接(Keep-Alive) - 浏览器有连接池,会复用到同一个 host:port 的连接 - 关键概念锚点 - RTT:网络交互的时间单位 - RTT = 一个来回的延迟 - 建连/TLS/重传都依赖 RTT - 高 RTT 下,交互次数越多越慢 - 吞吐与 BDP(带宽-时延积) - MTU 月 MSS - MTU:链路层最大承载 - MSS:TCP 单段可承载的应用数据最大值 - 包太大可能被分片或者丢弃 - 合理 MSS 能减少分片风险,提升稳定性 TCP 精讲 - TCP 精讲 - TCP 本质:在不可靠 IP 上提供可靠字节流 - TCP 提供内容 - 面向连接:双方都维护连接状态(序号、窗口、定时器等) - 可靠、有序:丢包重传、乱序重排、去重 - 字节流:没有消息边界(粘包/拆包的根源) - 流量控制:避免把对方接收缓冲打爆(rwnd),端到端 - 拥塞控制:避免把网络打爆,端到网络 - 报文段关键字段 - Sequence Number:本段数据在字节流里的起点序号 - Acknowledgment Number:我期望收到的下一个收到的序号 - Flags:SYN/ACK/FIN/RST/PSH/URG - Window(rwnd):接收端通告窗口 - Options:MSS,Window Scale - 连接建立:三次握手 - 标准流程 - C->S: SYN(seq=x) - S->C: SYN+ACK (seq=y,ack=x+1) - C->S: ACK (ack=y+1) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/17/20260217111458329.png,514,267) - 为什么必须三次 - 双方都要确认两件事 - 对方能收:对方收到了我的东西 - 对方能发:对方能把东西发回来让我收到 - 两次握手只能证明客户端 -> 服务端这条路通了;但是服务端无法证明服务端->客户端也通、且客户端确实收到了服务端的回应,第三次 ACK 就是让服务端拿到这个确认(服务端无法确认客户端能收) - 如果只有两次握手 - 服务端回 SYN + ACK 丢失了 - 服务端仍为连接建立,开始分配连接资源,等待数据 - 客户端实际上没有收到任何回应,会重试或放弃 - 三次握手还能防止历史重复 SYN 造成的“幽灵连接“ - 历史重复 SYN 指的是:某次旧连接尝试时发出的 SYN,由于网络拥塞、路由绕行、链路重传、设备缓存/异常等原因,在很久之后才到达服务器 - 如果服务器把旧 SYN 当新建连: - 服务器会分配资源 - 如果后续又出现旧 ACK 包,让服务器误以为连接已建立,交付错误的数据 - 幽灵连接:就是服务端自嗨式建立,客户端不认可 - 半连接队列与 SYN Flood - 服务端收到 SYN 后进入 SYN_RECV,会占用半连接资源(backlog) - SYN Flood:大量 SYN 不完成握手 —> 半连接队列被占满 -> 正常连接进不来 - 典型缓解:SYN cookies、增大 backlog、限速、丢弃策略、前置抗 DDoS - 连接关闭:四次挥手、半关闭 - 主动关闭方 A->B:FIN(不再发了) - B -> A:ACK (知道了) - B -> A:FIN (我也不再发了) - A -> B:ACK (知道了) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/17/20260217113908534.png,440,230) - 为什么常见是四次 - TCP 是全双工:A 不发 != B 不发 - B 可能还有数据要发完,所以 ACK 和 FIN 分开 - 半关闭 - A 发 FIN 表示 A 的发送方向关闭,但仍然可以接受 B 的数据 - 工程上常见于“请求发送完但继续接受响应流“的场景 - RST:不是正常关闭,是异常打断 - RST(Reset)是 TCP 里面非常硬的控制信号:立刻终止连接/拒绝连接 - 不是正常的四次挥手关闭,告诉对方:“这条连接不存在/状态不一致,马上停“ - RST 常见原因 - 端口没监听 - 应用崩溃/强制关闭 - 中间设备丢弃状态后注入 RST - TIME_WAIT、CLOSE_WAIT - 主动关闭方在发送最后一个 ACK 后进入 TIME_WAIT,并等待 2MSL。 - 必须 TIME_WAIT - 保证对方收到最后 ACK:最后 ACK 丢了,对方重发 FIN;TIME_WAIT 还能再次 ACK - 让旧连接的延迟报文过期:避免旧数据混入新连接 - 为什么是 2MSL - MSL = 报文在网络中可能存活的最大时间 - 等待 2MSL 是为了覆盖对方 FIN 重传 + 我 ACK 再次发送等往返的不确定性。 - TIME_WAIT 多了怎么办 - 常见场景:短连接高 QPS - 治理思路 - 优先做连接复用:HTTP Keep-Alive/连接池 - 调整应用行为:减少主动 close、合并请求、复用长连接 - 系统参数:允许端口更快复用等 - 通常是我方主动关 + 短连接 - CLOSE_WAIT 多是什么 - CLOSE_WAIT 表示:对方已经 FIN 了,但是我方应用还没有 close - 常见原因:应用层忘记关闭连接、连接泄露、线程阻塞导致没有走到 close - 通常是我方没关 - 可靠传输机制:序号、ACK、重传、SACK - 序号与累计 ACK - TCP 把数据看成连续字节流:每个字节都有序号 - ACK 是累计的:ack = N 表示 0..N-1 都收到了,下一步要 N - 好处:ACK 开销小 - 坏处:遇到乱序/丢包,单靠累计 ACK 不够精细 - 重传触发机制 - 超时重传(RTO):等不到 ACK 就重发(RTO 动态估计,基于 RTT 抖动) - TCP 发送一个数据段后,会期待收到 ACK,如果在某个时间窗口没有等到 ACK,TCP 可能认为丢了,于是触发超时重传。 - 等多久的时间阈值就是 RTO(Retransmission Timeout)。 - 触发条件:发送后,计时器到期,仍未收到能确认该段的 ACK - 行为:重传未确认的数据段 - RTO 动态估计 - 网络 RTT 会变,而且会抖动,RTO 必须跟随着链路实际 RTT 变化 - SRTT + RTTVAR -> RTO - 名词解释 - RTT sample:一次测得的 RTT(从发出某段到收到它对应 ACK 的时间) - SRTT (Smoothed RTT):平滑后的 RTT 均值。 - RTTVAR:RTT 的波动。 - RTO = SRTT + 4 * RTTVAR - SRTT 给出正常情况下 ACK 来回要多久 - RTTVAR 给出不确定性有多大 - 乘以 4 是给一个安全幅度:抖动越大,RTO 增长越明显,避免误重传 - 指数退避:超时一次,RTO 翻倍 - RTO 重传 vs 快速重传 - RTO 重传 - 触发:计时器到期,没等到 ACK - 特点:更“保底“,但通常更慢;而且代表网络更糟糕 - 常伴随:指数退避,cwnd 通常会大幅下降 - Fast Retransmit (快速重传) - 触发:重复 ACK - 特点:更快,更轻判拥塞 - 一半比 RTO 更温和 - 快速重传(Fast Retransmit):收到多个重复 ACK (典型 3 个)推断某段丢失了,立即重传) - RTO 重传局限性 - 必须等待一段时间没收到 ACK 才重传 - 在 RTT 较大或 RTO 比较保守时,恢复会很慢 - 快速重传的思路 - 不等超时,利用接收端反馈的 ACK 形态更早推断丢包 - 重复 ACK - 如果接收端收到了更后面的段,但是缺了中间某段 - 不能把乱序段交付给应用 - 会继续发送同一个累计 ACK,告诉发送端还在等从 1000 开始的那段,后面来的我先缓存 - ACK 值没前进,但又反复出现的 ACK 就是重复 ACK - 3 个重复 ACK 会触发快速重传 - 收到 3 个重复 ACK 代表某段大概率丢了 - 立即重传缺口对应的最早未确认段 - 快恢复 - 原因 - 收到快重传代表完了给没有完全堵死(还能持续收到 ACK) - 不必像 RTO 那样极度保守把发送速率打到很低 - Reno 直觉 - 触发快速重传后,认为发生拥塞 -> 降低 cwnd - 但利用重复 ACK 的到来,允许发送端在恢复期间适度继续发,避免完全停摆 - 当收到新的 ACK 后,退出快恢复,回到拥塞避免阶段 - SACK(选择确认) - 痛点:累计 ACK 信息不够 - 当出现丢包 + 乱序到达时,累计 ACK 只能不断重复同一个 ACK=N,发送端只知道缺口在 N,不知道 - 后面哪些段已经到了 - 究竟丢失了几段,丢失了哪几段 - 重传时是否把已到达的也重传 - 目标是把接收端实际收到的乱序块显式告诉发送端 - SACK 是什么:在 ACK 里带已收到的区间块 - SACK 是 TCP 的一个 Option,启用后 - 接收端在 ACK 报文的 TCP Options 中携带若干个 SACK blocks - 每个 block 描述一个已经收到的连续字节区间 - SACK block 的语义 - SACK block = (Left Edge, Right Edge) - 表示接收端已经收到:[LeftEdge, RightEdge) 这个区间 - ACK 字段仍然存在,表示最左侧连续已收前缀的下一个期待序号 - 启用流程:SACK Permitted - SACK 需要在三次握手时协商 - 双方在 SYN/SYN+ACK 中携带 SACK Permitted 选项 - 只要双方都声明支持,后续才会在 ACK 中携带 SACK blocks - 乱序与重排 - 网络层可能乱序,TCP 负责在接收端按序交付给应用 - 接收端会缓存乱序段,等缺口补齐再交付 - 滑动窗口 - 痛点 - 如果每发一段就必须等 ACK 再发下一段,吞吐会被 RTT 严重限制:链路大部分时间在等回音。 - 滑动窗口允许发送端在未收到 ACK 前,先发出一批数据,让数据在路上飞,从而把链路利用起来。 - 窗口 = 允许在途未确认数据的上限;窗口越大,在高 RTT 链路上容易跑满带宽 - rwnd(receive window,接收窗口,流量控制) - 由接收端通告(在 TCP 头的 Window 字段里) - 表示接收端缓冲区还剩多少空间 - 目的:防止发送端把接收端内存撑爆 - cwnd(congestion window,拥塞窗口,拥塞控制) - 由发送端维护 - 表示发送端认为网络当前能够承受的在途数据量上限 - 目的:防止把网络打爆 - swnd(send window,实际发送窗口) - 发送端真正允许“在途未确认“的上限 - 近似于 min(rwnd,cwnd) - 发送端视角:窗口如何限制发送 - 发送端维护两个关键指针 - SND.UNA:最早未确认的序号(UnACKed) - SND.NXT:下一个准备发送的序号(Next to send) - 窗口约束可以理解为: - 允许发送的序号范围:[SND.UNA, SND.UNA + swnd) - 已发送未确认的范围:[SND.UNA, SND.NXT) - 还能继续发送的额度:swnd - (SND.NXT - SND.UNA) - ACK 到来时发生什么 - 收到 ACK,把 SND.UNA 往前推进 - 已确认数据从窗口左边滑出 - 窗口右边腾出了空间,允许继续发送新的数据 - 接收端视角:为什么会出现 dupACK/乱序缓存 - 接收端维护两个关键指针 - RCV.NXT:下一段按序期望的序号 - 接收缓冲:可以暂存乱序到达的段 - 出现乱序 - 接收端会缓存后到的段,但 ACK 仍然回 RCV.NXT(累计 ACK) - 发送端看到重复 ACK (dupACK),可能触发快速重传 - 窗口、吞吐、RTT - 吞吐上限 = 窗口大小/RTT - RTT=100ms,窗口=64KB,吞吐=64KB/0.1s=640KB/s - BDP(带宽-时延积),窗口需要=BDP 才能跑满带宽 - 表示一条网络路径在任意时刻在路上飞着的数据量大概有多少,也叫管道容量 - BDP = 带宽 * 往返时延 (RTT) - 想要把链路跑满,发送端需要让“在途未确认数据“接近 BDP - 在途未确认数据主要受窗口限制:in_flight <= min(cwnd, rwnd) - 要跑满带宽,需要窗口大小 >= BDP - 零窗口与窗口探测 - 如果接收端缓冲满了,会通告 rwnd=0 - 发送端此时必须停止发送新数据,但是要避免死锁 - TCP 有 persist timer:周期性发送 Zero Window Probe 探测包 - 一旦接收窗口恢复,发送端继续发 - 粘包/拆包 - 粘包:在发送端 send() 了两条业务消息,接收端一次 recv() 读到了两条合在一起的字节。 - 拆包:在发送端 send() 了一条业务消息,接收端需要 recv() 多次才能读完 - TCP 不保证 send 一次就对应端 recv 一次 - 原因 - TCP 的抽象是流 - 像读文件一样读一串字节 - send():把字节追加到发送缓冲区 - recv():从接收缓冲区取走当前可读的字节 - 分段由 TCP 决定,不由应用决定 - 接收端读取粒度由应用决定 - 解法 - 在应用层定义消息边界 - 长度前缀(Length-Prefixed,最通用) - 格式 | 4-byte length (big-endian) | payload bytes... | - length 表示 payload 长度 - 接收端先读满 4 字节得到 length,再继续读满 length 字节 - 分隔符(Delimiter-Based,适合文本协议) - 行协议:以 \r\n 结尾 - 固定长度(Fixed-Size) - 每条消息固定 N 字节 - 拥塞控制 - 拥塞不是对端瘦不下,那是流量控制 rwnd,拥塞是网络路径上的队列/链路承载不住 - 路由器/交换机队列堆积 -> RTT - 队列溢出 -> 丢包 - 丢包/排队抖动 -> 吞吐下降、延迟暴涨 - 拥塞控制控制的是发送端允许在网络中飞着的未确认数量,cwnd - Reno 的两个核心状态变量 - cwnd:拥塞窗口(发送端认为网络现在能承受的在途数据量) - ssthresh:慢启动阈值,决定从指数增长到线性增长的分界点 - 四个阶段 - 慢启动 - 目的:刚开始不知道网络能承受多少,先快速试探 - 规则: - 初始 cwnd 为一个较小值,历史是 1MSS - 每收到一个 ACK,cwnd 增加 1MSS - 因为一个 RTT 内会收到大约 cwnd/MSS 个 ACK,所以每个 RTT cwnd 近似翻倍 - 每 RTT 把在路上的包数翻倍,迅速逼近瓶颈带宽 - 太快可能把队列灌满,引发丢包 - 拥塞避免 - 当 cwnd 到达或超过 ssthresh,从慢启动切换到拥塞避免 - 每 RTT 让 cwnd 大约增加 1 MSS,每个 ACK 增加 MSS*MSS/cwnd - 大多数时间连接处于的阶段 - 快重传:用 dupACK 提前判断丢包,不等 RTO - 触发条件 - 收到 3 个重复 ACK dupACK - ACK 号不前进,连续出现多次 - 快恢复:丢包后减速但不中断,比 RTO 温和 - 发生快重传后的关键动作 - 设定新的值 - ssthresh = cwnd/2 - cwnd 不回到很小,而是进入快恢复逻辑 - 当收到能够推进 ACK 的新 ACK,退出快恢复,进入拥塞避免 - RTO vs dupACK - RTO:最严重,认为网络可能很糟 - ssthresh = cwnd / 2 - cwnd 下降到很小,经典是 1MSS - 回到慢启动(重新探测) - RTO 通常会指数退避,导致恢复更慢 HTTP 与 Web 体系 - HTTP 与 Web 体系 - HTTP 基础语义 - HTTP 是无状态应用层协议:每个请求都自包含信息,状态由 Cookie/Token/Session 等机制在应用层实现 - HTTP 定义的语义,具体传输方式由 HTTP/1.1、HTTP/2、HTTP/3 决定 - 方法:语义 + 幂等 + 安全性 - GET:获取资源,应当安全+幂等 - HEAD:只要响应头,不要响应体,用作探测/缓存探测 - POST:提交数据,创建子资源或触发动作,通常非幂等 - PUT:整体替换/创建指定资源,通常幂等 - PATCH:部分更新,是否幂等取决于语义设计 - DELETE:删除资源,通常幂等 - OPTIONS:探测服务器能力(常用于 CORS 预检) - 状态码 - 1xx:信息(少见) - 2xx:成功 - 200 OK - 201 Created 创建成功 - 204 No Content 成功但无响应体 - 3xx:重定向 - 301 永久 - 302 临时(历史语义混乱) - 303 让客户端用 GET 拿 - 307/308 保持方法不变 - 4xx:客户端错误 - 400 参数/格式问题 - 401 需要认证 - 403 拒绝 - 404 不存在 - 409 冲突 - 429 限流 - 5xx:服务器错误 - 500、502 上游坏/网关问题 - 503 不可用 - 504 上游超时 - Header - 通用与内容相关 - Content-Type:实体类型(json/html...) - Content-Length:长度(HTTP/1.1 重要) - Content-Encoding:gzip/br 等压缩 - Accept/Accept-Encoding/Accept-Language:内容协商 - Host:HTTP/1.1 必须(虚拟主机) - Connect:连接管理(HTTP/2 中不再使用) - 会话与安全 - Cookie/Set-Cookie - Authorization (Bearer/Basic 等) - Origin/Refer:CORS/CSRF 相关 - Strict-Transport-Security:强制 HTTPS - Content-Security-Policy(CSP):前端安全策略 - 缓存相关 - Cahce-Control - Expires - ETag/If-None-Match - Last-Modified/If-Modified-Since - Vary - Age (代理缓存的年龄) - Pragma:no-cache (历史兼容) - HTTP 缓存 - 两类缓存:强缓存vs协商缓存 - 强缓存 - 命中强缓存时浏览器直接用本地缓存,不走网络 - 典型控制 - Cache-Control: max-age= - Expires:老机制 - 协商缓存(发请求但可能不下载) - 浏览器发请求带校验文件,服务器判断资源是否变化 - ETag 流程 - 响应:ETag: "xxx" - 下次请求:If-None-Match: "xxx" - 若未变:返回 304 Not Modified - Last-Modified 流程 - 响应:Last-Modified: ... - 下次请求:If-Modified-Since:... - 未变:304 - Cache-Control 关键指令 - max-age=N:资源在 N 秒内视为新鲜(强缓存) - no-store:绝对不缓存 - no-cache:可以缓存,但每次使用前必须去服务器验证 - must-revalidate:过期后必须验证,不能用陈旧副本 - public:允许被共享缓存(CDN/代理)缓存 - private:只允许浏览器私有缓存,不允许共享缓存缓存 - s-maxage=N:给共享缓存的 max-age - Vary 存储维度的分桶键 - Vary 告诉缓存,同一个 URL 的响应会因为某些请求头不同而不同 - Vary:Accept-Encoding,gzip/br 不同版本要分开缓存 - Vary:Origin:CORS 常见 - Vary 过多会让缓存碎片化,命中率下降 - ETag 的强弱、代理与一致性 - 强 ETag:字节级一致才算命中 - 若 ETag (W/):语义上等价即可 - HTTP/1.1:连接、报文、队头阻塞 - HTTP/1.1 报文与传输 - 文本协议:请求行 + 头 + 空行 + body - 连接默认与持久化:Keep-Alive (减少握手成本) - Chunked Transfer-Encoding:服务端可流式传输,不提前知道 Content-Length - HTTP/1.1 的性能问题:应用层 HOL - 同一连接上请求/响应按顺序进行 - 一个慢请求会阻塞后续的请求(队头阻塞) - HTTP/2:二进制分帧与多路复用 - HTTP/2 为什么快 - 二进制分帧:把大消息拆成帧 - 多路复用:多个 stream 在同一 TCP 连接上并发交错传输 - 头部压缩:HPACK(减少重复头部开销) - HTTP/2 性能问题:TCP 层 HOL(队头阻塞) - TCP 一旦丢包,需要重传并按序交付 - 丢的那段之前的洞补不上,后续数据即使到了也不能交给上层 - 结果:一个丢包会让同连接上所有 stream 都受影响 - HTTP/3/QUIC - 核心变化 - HTTP/3 跑在 QUIC 上,QUIC 基于 UDP - QUIC 自己实现可靠传输与拥塞控制 - 按 stream 提供独立的有序交付,一个 stream 丢包不影响其他 stream 的交付 - TLS 与 HTTPS - 位置与成本 - HTTPS = TCP 建连后再 TLS 握手,再 HTTP - 首次访问慢常见原因:DNS + TCP + TLS 多次 RTT - 解决的问题 - 身份认证:验证连接的是域名的真实服务器 - 机密性:传输内容加密 - 完整性:防篡改 - TLS 握手 - TLS 1.2 - ClientHello:客户端支持的版本/套件,随机数,SNI 等 - ServerHello:选定版本/套件、随机数,让双方对本次会话参数单程一致 - Certificate:服务器发送证书链,客户端据此验证:链是否可信、域名是否匹配、是否过期,用途是否正确 - ServerKeyExchange:服务器提供 ECDHE 参数与公钥,客户端通过验证签名,确保密钥协商参数的安全性 - ServerHelloDone:告诉客户端,材料已经发完 - ClientKeyExchange:客户端协商参数,客户端发送自己的 ECDHE 公钥,双方据此计算共享 secret - ChangeCipherSpec + Finished:双方切换到加密通信并校验握手完整性 - Web 会话:Cookie/Session/Token - Web 会话解决的问题 - 认证:你是谁 - 授权:你能做什么 - 会话保持:登录态怎么持续、怎么过期、怎么注销 - 安全边界:防窃取、防伪造、防重放、防跨站 - Cookie:浏览器自动携带的会话容器 - Cookie 是浏览器保存的一组 key=value,由服务器通过响应头 Set-Cookie 下发。 - 后续请求中,浏览器会按照规则自动带上 Cookie 头,无需 JS 参与 - Cookie 关键属性 - Domain/Path:决定发送范围 - Domain=example.con:子域也可能携带 - 不设置 Domain:仅当前 host - Path=/api:只对当前路径生效 - Expires/Max-age:决定生命周期 - Max-Age=0:立即删除 - Session Cookie:不设过期时间,随浏览器进程结束而清理 - Secure:只在 HTTPS 发送 - HttpOnly:禁止 JS 读取 - 有 HttpOnly 的 cookie,JS 读取不到 - 不能防止 CSRF - SameSite:控制跨站请求是否携带 - Strict:几乎不跨站携带 - Lax:默认常用 - None:允许跨站携带,必须同时设置 Secure - Session:服务端保存状态,客户端只存门票 - Session 的典型模式 - 服务端维护 session_id -> session_data - 客户端用 Cookie 存 session_id - 每次请求:浏览器自动带 cookie -> 服务端查 session store -> 识别用户 - Token(尤其是 JWT):把身份证明带在请求里 - Token 的核心模式 - 服务端签发 token 给客户端 - 客户端后续请求带 token - 服务端验证 token 来识别用户 - JWT 结构 - JWT=header.payload.signature - header:算法等元信息 - payload:claims 声明 - signature:服务端用密钥签名 - Refresh Token 与 Access Token - Access Token:短有效期,每次用于请求 - Refresh Token:长有效期,用于换取新的 Access Token - 安全边界:XSS、CSRF、会话固定、重放 - XSS(跨站脚本) - 若 token/cookie 可以被 JS 读取,xss 可以直接窃取并盗用 - cookie 用 HttpOnly + Secure - CSP 减少脚本注入 - 输入输出编码 + 模板转义 - CSRF 跨站请求伪造 - CSRF 利用浏览器自动携带 cookie - 攻击者诱导用户在登录态下访问恶意页面,发起跨站请求到受害站点 - 防护手段 - SameSite Cookie (优先) - CSRF Token(表单/请求头携带,服务端校验) - 检查 Origin/Rerfer - 同源策略与 CORS - 痛点 - 浏览器安全模型默认假设:不同站点之间互不信任 - 浏览器用同源策略(Same-Origin Policy,SOP)限制一个网页脚本能读取另一个来源的资源内容 - 业务需要跨域调用 API,有了 CORS(CrossOrigin Resource Sharing):在 SOP 的基础上提供一个受控的放行机制。 - Orgin 源是什么:同源的三元组 - Origin = scheme + host + port - https://example.com:443 - 同源策略具体限制 - 跨源读被禁止 - JS 用 fetch 请求 https://xxx.com,请求可能能发出去,但是浏览器会阻止 JS 读取响应,除非响应满足 CORS 规则 - 跨源写/发送很多情况下是允许的 - <form action="xxx.com" method="POST"> 可以提交,因此 CSRF 能成立,请求可以发出去,但是页面读取不到 bank.com 的响应内容 - 部分资源加载天然跨源允许 - <img> <scirpt> <link> 浏览器允许加载这些资源 - CORS 本质:服务器声明允许,浏览器执行拦截 - 浏览器在请求带上 Origin - 服务器在响应返回 Access-Control-* 头 - 浏览器据此决定:是否把响应暴露给 JS - CORS 两种请求路径:简单请求 vs 预检请求 - 简单请求 - 方法是 GET/HEAD/POST - Content-Type 只能是三种之一 - application/x-www-form-urlencoded - multipart/form-data - text/plain - 请求头只能是 CORS-safelistedhead,不能随意加自定义头 - 预检请求 - 不满足简单请求条件,浏览器会先发送一个 OPTIONS 请求,询问服务器是否允许 - 预检请求携带关键头 - Origin: https://a.com - Access-Control-Request-Method:PUT - Access-Control-Request-Headers:x-token(将要携带的自定义头列表) - 服务器如果允许,需要在 OPTIONS 响应返回 - Access-Control-Allow-Origin - Access-Control-Allow-Methods - Access-Control-Allow-Headers - Access-Control-Max-Age(预检缓存多久) - 通过后,浏览器才会发真实请求 - CORS 响应头:每个头怎么用 - Access-Control-Allow-Origin - 指定允许的 Origin - 可以是精确 Origin 或者 * (允许任意源),如果要带 Cookie/凭证,不能用 * - Access-Control-Allow-Methods - 允许的方法列表:GET/POST - 主要用于预检响应 - Access-Control-Allow-Headers - 允许的自定义请求头列表 - 必须覆盖预检中 Access-Control-Request-Headers 请求到的那些头 - Access-Control-Allow-Credentials - true 表示允许携带凭证,例如 Cookie - 配合前端请求 credentials: 'include' - Access-Control-Expose-Headers - 默认情况下,JS 能够读取的响应头很有限,如果希望暴露,必须显式添加 - Vary: Origin - 按 Origin 分桶缓存 - DNS 解析 - 基础对象 - 记录类型 - A(IPv4) - AAAA(IPv6) - CNAME(别名) - NS(权威) - 解析角色 - Stub resolver(客户端/OS) - Recursive resolver(递归解析器:运营商/公司 DNS/公共 DNS) - Authoritative server (权威 DNS) - 正向代理、反向代理、透明代理与隧道 - 代理:本质上是位于客户端与服务端之间的一种中间节点,代表一侧发起连接、转发数据、并可能施加策略 - 承担职责 - 路由/转发:把请求送到正确上游 - 安全控制:鉴权、WAF、访问控制 - 性能优化:连接复用、压缩、缓存、限流 - 可观测性:日志、指标、追踪、采样 - 协议转换:HTTP <-> grpc 等 - 正向代理 - 正向代理站在客户端一侧,客户端明确知道自己在用代理,由代理代表客户端访问外部资源 - 反向代理 - 反向代理站在服务端一侧,对外它像真正的服务器入口,对内把请求转发到后端服务 - 反向代理常用原因 - 隐藏后端拓扑:外部只有一个入口 - 统一 TLS 终止:证书集中管理 - 统一流量治理:限流、熔断、灰度、路由、鉴权 - 连接复用:对外维持大量连接、对内复用少量连接 - 观测与审计:同一日志/指标/链路追踪 - 透明代理 - 隧道:Connect 到底干了什么 - 隧道:代理不解析上层协议,只做字节透传。 - HTTP Connect 隧道流程 - 客户端先对代理发 CONNECT - 客户端 -> 代理 - CONNECT bank.com:443 HTTP/1.1 - Host: back.com:443 - 代理建立到目标的 TCP 连接 - 代理 -> bank.com:443:发起 TCP 连接 - 代理回客户端 - HTTP/1.1 200 Connect Established - 隧道建立,字节透传 - 客户端与 bank.com 的 TLS 握手数据会被原封不动地通过代理转发,代理看不懂也不需要看懂 - TLS 会话在客户端<->目标服务器之间建立 - 负载均衡:L4 vs L7、算法、会话保持、健康检查 - 目标 - 扩展性:把流量分散到多台实例,提高吞吐 - 高可用:实例故障自动摘除,流量不中断或快速恢复 - 性能:就近、减少重连、减少排队、提升尾延迟 - 治理:灰度、限流、熔断、路由、可观测 - 边界 - LB 解决的是把请求推送到谁,不解决业务正确性 - LB 层任何自动重试/超时,都可能改变业务语义 - L4 与 L7 的区别 - L4 LB(传输层):基于 IP/端口/连接做转发 - 优点:性能高,对协议透明 - 缺点:无法基于 URL/Header 做路由 - 常见实现:内核转发、四层 VIP、DSR、NAT 模式等 - 适合长连接:WebSocket、MQ、数据库连接等 - L7 LB(应用层):理解 HTTP,按照 Host/Path/Header/Cookie 路由 - 常见常见:API Gateway、Ingress、CDN 回源入口 - 负载均衡算法 - Round Robin(轮询) - 依次分发到每个后端 - 适合:请求耗时相近、后端同质、短连接 - 问题 - 后端性能差异大时不均衡 - 长连接场景下连接一旦分配就会粘住,RR 也会失衡 - Weighted Round Robin - 按权重分配(机器规格不同,冷热分层) - 适合:异构机器,逐步扩容 - 问题:权重需要维护;负载时间随时间变化时仍然不准 - Least Connections(最少连接) - 连接数最少的后端连接 - 适合:长连接、连接数与负载相关的业务 - 问题: - 连接数不等于负载 - 需要 LB 维护连接计数状态 - Least Request/Least Load(最少请求/最小负载) - 按当前活跃请求数或综合指标选择 - 适合:请求耗时差异大,尾延迟敏感 - 问题:实现复杂、依赖观测准确性;指标延迟可能导致震荡 - Random/Power of Two Choices(随机/二选一) - 随机挑两个后端,选最优者 - 优点:简单且实际效果接近最优,分布更稳定,大规模下能显著降低热点概率 - 适合:大规模集群、需要避免中心化状态 - Consistent Hash(一致性 hash) - 按 key 映射到固定节点 - 适合: - 需要缓存命中(本地 cache) - 会话保持(不想引入共享 session) - 分片存储(按 key 分片) - 关键性质:节点增减时迁移量小 - 问题: - 热点 key 会压垮单节点(热点拆分/虚拟节点) - 节点故障时 key 重映射会引发抖动。 - 会话保持 - 为什么需要 sticky - 后端有状态 - 本地缓存必须命中 - WebSocket/长连接绑定到某实例 - 常见 sticky 手段 - Source IP hash - 按客户端 IP 做 hash 选节点 - 优点:简单 - 缺点: - NAT/代理下大量用户共享同一出口 IP -> 严重倾斜 - 用户换网 IP 变化导致漂移 - Cookie 粘性 - LB 注入 cookie ,后续按照 cookie 路由 - 优点:比 IP 稳定 - 缺点:跨域,隐私策略,cookie 清理,灰度/回源复杂 - 一致性哈希(按 userId/会话键) - 工程风险 - 降低容错:节点挂了,该节点的用户都受影响 - 产生热点:某些用户/租户流量大->单节点热 - 扩缩容抖动:重新分配导致缓存冷启动、延迟尖刺 - 推荐的替代方案 - 应用无状态 + 共享 session (Redis) - 把状态外置(DB/Cache),实例可以随时替换 - 长连接常见:连接层 sticky 不可避免,要做好限流与迁移策略 - 健康检查 - L4 健康检查 - 探测 TCP 能否 connect 成功 - 优点:便宜、通用 - 缺点:应用假活时仍然会通过(线程池满、依赖挂) - L7 健康检查 - 请求 /healthz 或 /readyz - 可检查 - 进程存活 - 是否可接新流量 - 关键依赖是否可用 - 设计要点 - liveness vs readliness 分离 - liveness:进程是否需要重启 - readliness:是否接流量 - 避免健康检查过重 - 如果每秒大量探测并且检查依赖,会把依赖也打挂 - 阈值与抖动控制 - 连续 N 次失败才摘除 - 连续 M 次成功才恢复 - 慢启动与预热 - 新实例启动后先不接流量,预热缓存/JIT,再标 ready - 网络排障 - L3/L4 基础联通与路径 - ping - 测试的是什么 - ping 用的是 ICMP Echo Request/Echo Reply - 发 Echo Request(带序号 seq、时间戳等) - 对端回 Echo Reply - 测量往返时延 RTT - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218103650584.png,492,184) - 查看延迟 - 看 avg 和 min/max - 抖动 - RTT 的波动程度 - stddev(标准差) 大,说明网络队列/无线/拥塞波动明显 - 丢包 - packet loss > 0 说明有包没有收到回包 - tracerout/mtr 的原理 - traceroute:用 TTL 把路径探测出来 - IP 包里有 TTL,每过一跳路由器把 TTL - 1 - 当 TTL 变为 0,路由器丢包并回 ICMP Time Exceeded - traceroute 依次发 TTL=1,2,3 的探测包 - 可以看到每一跳的地址与该跳的 RTT - 每一跳显式的 RTT 是你->该跳->你的往返,不是单程,也不是该跳->下一跳的延迟 - mtr:traceroute + ping 的结合 - mtr 会对每一跳持续发探测包并统计 - 丢包率 - 最快/平均/最慢 RTT - 抖动 - mtr 更适合定位抖动/偶发丢包/拥塞点 - 用 traceroute 看路径与绕路 -

二月 16, 2026

投资

投资基础认知 - 投资基础认知 - 什么是投资 - 储蓄 - 把钱存起来,追求安全,收益很低 - 保住本金,几乎没有风险,也跑不赢通胀 - 投资 - 将钱投入到能长期产生回报的资产中 - 有逻辑依据,关注长期价值、承担合理风险以获取合理回报 - 投机 - 试图通过短期价格波动赚差价 - 投机接近于赌方向,往往没有对资产本身价值的深入分析,风险很高 - 资产与负债 - 资产 - 能把钱放进你口袋的东西 - 买了一套房,每月收租金 3000 元 - 负债 - 从口袋里面掏钱的东西 - 买了一套房,每月还房贷 8000 元 - 资产和负债的区别在于是给你创造现金流还是消耗现金流 - 复利的力量 - 单利 - 投入 10000,年化收益 10%,每年赚 1000,10 年后有 20000 - 复利 - 同样投入 10000,第一年 11000,第二年 12100,利息变成利息,十年后有 25937 - 公式:终值 = 本金 x (1 + 收益率) ^ 年数 - 重要的不是能赚多高的收益率,而是能让钱复利多长时间 - 通货膨胀 - 本质:同样一笔钱,买到的东西变少 - CPI(消费者价格指数):是通胀的衡量标准,追踪一篮子日常消费品的价格变化 - 虽然 CPI 每年只有 2%-3%,但是实际感知到的通胀更高,房价、教育、医疗这些大头支出涨幅远超 CPI - 发生的原因 - 货币超发,央行印了更多的钱,但是东西没多 - 需求拉动或者成本推动,原材料涨价 - 投资收益率必须跑赢通胀,否则财富实际上是缩水的 - 实际收益率 = 名义收益率 - 通胀率 - 风险与收益 - 风险排序:银行存款->货币基金->国债->企业债券->债券基金->股票指数基金->个股->期货期权->加密货币 - 系统性风险:整个市场的风险,比如经济衰退、金融危机、战争,所有资产都会跌 - 非系统性风险:某个公司或行业特有的风险,可以通过分散投资来大幅降低风险 - 分散投资能消除非系统性风险,无法消除系统性风险 - 主要投资品种概览 - 股票 - 花钱买了一家公司的一小块所有权 - 赚钱方式 - 价差:50 块买入,80 块卖出 - 分红:赚钱的公司把利润分一部分给股东,叫做股息或分红 - 银行股股价 5 元,每年每股分红 0.3 元,股息率就是 6% - 股票特点:长期收益潜力最高,但是短期波动大,适合长期持有 - 市值:股价乘以总股数,代表市场认为这家公司值多少钱 - 大盘股:指市值几百亿以上的公司,波动相对较小,比较稳 - 小盘股:市值较小,波动大,涨跌剧烈 - 涨跌停:A 股特有的规则,普通股票每天最多涨/跌 10%,创业板和科创板是 20% - 债券 - 把钱借给别人,对方承诺按时还本付息 - 发行方分类 - 国债:政府发行,信用最高,基本不违约,但是收益率也最低 - 地方政府和企业债:信用等级低于国债,利率会更高 - 可转债:是债券但是可以在指定条件下转换成股票 - 债券和利率是反向关系,市场利率上升,已发行债券的价格就下降 - 基金 - 基金的本质是众筹投资:很多人凑钱给基金经理,由他决定买什么 - 基金购买的是基金经理帮忙搭配好的一篮子资产 - 解决的问题 - 钱少,买不了那么多股票来分散风险 - 不专业,选股的事给专业的人做 - 投资方向分类 - 股票型基金 - 债权型基金 - 混合性基金 - 货币基金 - 管理方式分类 - 主动性基金:基金尽力主动选择,管理费较高(1.2%-1.5%) - 被动型基金:直接复制某个指数的成分股,管理费很低(0.1%-0.5%) - 长期来看,大多数主动型基金经理跑不赢指数 - 关注的核心指标 - 费率 - 规模 - 历史业绩 - 基金经理的从业年限和风格 - ETF - 全称是“交易所交易基金“,可以理解为是一只可以像股票一样在交易所买卖的基金 - 和普通基金的区别在交易方式上 - 普通基金在支付宝或者银行 app 申购赎回,一天只有一个价格(收盘后结算),到账通常 T+1 或者更久 - ETF 直接在股票交易软件里买卖,和股票一样实时报价、实时成交,流动性好的多 - ETF 大多是被动型的,跟着一个指数 - ETF 的费率很低 - 大宗商品与加密货币 - 大宗商品指的是黄金、白银、石油、铜、大豆这类标准化的原材料 - 普通人参与大宗商品的方式是买黄金或者相关的 ETF - 黄金被称为避险资产,当股市暴跌,通胀飙升或地缘政治紧张,资金会涌入黄金避险,推高金价 - 黄金不产生任何现金流,不付利息也不分红,长期收益率不如股票 - 加密货币以比特币和以太坊为代表 - 比特币的设计理念是总量固定 2100 万枚,不受任何政府控制的数字货币 - 以太坊是一个可以运行智能合约的平台,ETH 是这个平台的燃料 - 加密货币波动性极高,24 小时不间断交易,没有涨跌停限制,全球流通,机会和风险都极大 - 新手建议只碰比特币和以太坊两个主流币 - 开户与交易规则 - 投资不同品种需要不同的账户 - 证券账户是买卖股票和场内 ETF 的,需要在一家证券公司(券商)开户,开完后得到一个资金账户和股东账户 - 基金账户是买场外基金用的,可以通过银行 APP、基金公司 APP、第三方平台 - 加密货币账户需要在交易所注册 - 银行账户是所有投资的资金中转站,证券账户需要绑定一张银行卡,通过银证转账把钱转进转出 - 交易规则和费用 - 交易时间 - 交易时间是每个工作日的上午 9:30-11:30 和下午的 13:00-15:00 - 9:15-9:25 是集合竞价时间,决定开盘价 - 美股的交易时间换算成北京时间是晚上 21:30 到次日凌晨 4:00,冬令时推迟一个小时 - 加密货币是 7x24h 全天候交易 - 交易单位 - A 股买入最少 100 股(一手),卖出没有这个限制 - ETF 通常 100 份起买,但是有些券商支持拆零 - 费用 - 券商佣金 - 券商佣金是每次买卖股票或者 ETF 券商收的手续费,大概是万2.5-万 3,买卖各收一次 - 很多券商会有最低 5 元的门槛 - 开户的时候可以和客户经理谈佣金率,通常能谈到万 1.5 甚至更低 - 印花税 - 是国家收的,目前税率是千分之 0.5,只在卖出股票时收 - 过户费 - 很少,每 10 万收 1 元,可以忽略不计 - 基金费用 - 申购费是买入时收,股票型基金 1.5%,第三方平台通常打一折只要 0.15% - 赎回费根据持有时间不同,持有越久费率越低,超过一定时间免赎回费 - 管理费和托管费是每天从基金净值里扣,看到的基金收益已经扣过了 - 读懂行情软件 - 行情软件核心信息 - 现价:这只股票此刻的交易价格,也叫最新价,几秒钟就会跳动一次 - 涨跌幅:今天相比昨天收盘价涨了或跌了百分之几 - 开盘价:今天第一笔交易的成交价,由早上 9:15-9:25 集合竞价决定 - 收盘价:当天最后的价格,15:00 收盘时确定 - 最高价和最低价:今天盘中达到的极端值 - 成交量:今天到目前为止总共成交了多少股,成交量突然放大说明有大资金在行动,需要注意 - 成交额:总共成交了多少钱 - 换手率:今天成交的股数占这只股票总流通股数的百分比,换手率 1-3% 正常,5% 比较活跃,10% 非常热烈 - 总市值:股价乘以总股本,代表市场给这家公司的总定价 - 流通市值:市场上能自由买卖的那部分股份,因为很多大股东的股份是有锁定期的 - K 线图 - 是 200 多年前日本米商发明的价格记录方法 - 每一根 K 线代表一个时间周期内的四个价格:开盘价、收盘价、最高价、最低价 - 如果收盘价高于开盘价,说明这个周期价格涨了,K 线画成红色,中间的实心方块叫做阳线 - 方块的下边缘是开盘价,上边缘是收盘价 - 如果收盘价低于开盘价,说明价格低了,画成绿色,叫阴线 - 方块上下伸出的线叫做影线,上影线的顶端是最高价,下影线的底段是最低价 - 上影线越长说明上方抛压越大,价格冲高后被打回来了 - 下影线越长说明下方有支撑,价格跌下去又被拉起来了 - 方块越长说明开盘价和收盘价差距越大,当天的趋势越明显 - 方块很小说明多空双方力量接近,市场在犹豫 - 日 K 代表一天,周 K 代表一周 - K 线下方还有成交量柱,红色柱子对应上涨,绿色柱子对应下跌 - 价格上涨同时成交量放大,说明涨势有资金支撑,比较健康 - 价格上涨但是成交量萎缩,可能是虚涨,持续性存疑 - 均线初识 - K 线图上叠加的几条彩色的曲线就是均线(MA) - 均线把过去 N 天的收盘价求平均值连成一条线 - MA5 是 5 日均线,反映最近一周的平均成本 - MA20 是 20 日均线,反映一个月的平均成本 - MA60 是 60 日均线,代表一个季度 - MA250(年线),代表一年 - 基本逻辑 - 股价在均线上方,代表近期买入的人平均是赚钱的,市场情绪偏乐观 - 股价在均线下方,代表大部分人被套,情绪偏悲观 - 短期均线在长期均线上方被视为上升趋势,否则是下降趋势 - 什么是指数 - 指数的本质 - 指数就是从市场里面挑出一篮子股票,按照某种规则计算出来的一个数字,用来代表这一篮子股票的整体表现 - 指数本身不能买卖,它只是一个统计数字 - 可以通过购买跟踪指数的 ETF 或者指数基金来间接买入指数 - 指数由专门的指数公司编制和维护,中国由中证指数公司编制,美国由标普道琼斯、MSCI等公司编制 - 指数会定期调整成分股,把不合格的公司踢出去,把新的优质公司纳入进来 - 指数自带优胜劣汰,长期看质量不会越来越差 - 核心指数 - 中国市场 - 上证指数 - 涵盖了上海证券交易所的所有股票 - 按总市值加权,包含大量银行、石油等大盘股 - 包含所有股票不含优劣 - 沪深 300 指数 - 从沪深两市挑出市值最大、流动性最好的 300 只股票,按自由市值加权 - 覆盖了 A 股越 6 成的总市值,是公认的最能代表 A 股大盘蓝筹表现的指数 - 中证 500 指数 - 排除沪深 300 之后的 500 只中等市值股票 - 代表中盘成长股的表现 - 沪深 300 代表大公司,中证 500 代表小公司 - 创业板指数 - 从创业板挑出 100 只市值最大的股票,科技和医药公司占比高,波动比沪深大,弹性也更强 - 美国市场 - 标普 500 指数 - 由美国市值最大的 500 家公司组成,覆盖每股 80% 的总市值 - 长期年化收益率大概在 10% 左右 - 纳斯达克 100 指数 - 从纳斯达克交易所挑出 100 家最大的非金融公司,科技股权重极高 - 道琼斯工业指数 - 只包含 30 只股票,用的是价格加权,专业性不高,但是历史悠久 - 香港市场 - 恒生指数 - 日本市场 - 日经 225 指数 - 全球市场 - MSCI 全球指数 - 富时全球指数 - 什么是 ETF - ETF 的运作机制 - ETF 背后有一家交易公司管理 - 基金公司的任务是买入指数所有成分股,按照指数中每只股票的权重来配置 - 交易所买到的 ETF 份额,代表了这一篮子股票的一小部分所有权 - ETF 价格会紧贴它所跟踪的指数走势 - IOPV(参考净值):交易时段每 15s 更新一次,告诉 ETF 底层那一篮子股票此刻值多少钱 - 如果 ETF 的市场价格明显高于或低于 IOPV,说明此时出现了溢价或折价,不建议买入 - 如何挑选一支好的 ETF - 跟踪误差 - 衡量 ETF 的实际表现和它所跟踪的指数之间的偏差 - 跟踪误差越小,说明 ETF 越忠实 - 年化跟踪误差控制在 0.1% 以内的算优秀 - 费率 - ETF 的费用主要包含管理费和托管费,加起来每年 0.2%~0.6% 之间 - 规模 - 规模太小会出现买卖大的价差,或者被基金公司清盘 - 建议选规模在 10 亿元以上的 ETF - 流动性 - 成交额越大,说明买卖的人越多 - 日均成交额在 1 亿以上的 ETF 就算不错 - 成立时间 - 运作时间越长的 ETF,数据越充分,管理经验越丰富 - 优选 3 年以上的 - 常见 ETF 分类 - 宽基 ETF - 代表整个市场的宽基指数 - 例如沪深 300 ETF、中证 500 ETF - 行业 ETF - 跟踪某个特定行业的指数 - 例如医药 ETF、半导体 ETF、消费 ETF,集中度高,波动大 - 策略 ETF - 不按市值加权,而是按照某种因子策略来选股和加权 - 例如红利 ETF 选择高分红的公司,低波 ETF 选择波动低的公司 - 跨境 ETF - 用人民币投资海外市场 - 可能会有较大溢价,需要看溢价率 - 债券 ETF - 跟踪债券指数 - 商品 ETF - 跟踪大宗商品价格 - 什么是债券 - 核心要素 - 面值 - 债券到期时发行方要还给你的本金,通常是 100 元一张 - 面值和买入价不一定相同,如果在二级市场买,可能要 101 一张(溢价买入)或者 98 一张(折价买入) - 票面利率(息票率) - 发行方承诺每年按面值的百分之几付利息 - 期限 - 从发行到到期还本的时间长度 - 短期债一般 1 年以内,中期 1-10 年,长期 10 年以上 - 到期收益率(YTM) - 考虑买入价格,未来收到的所有利息和到期拿回的本金,算出的年化实际回报率 - 到期收益率才是真正能拿到的回报率 - 信用评级 - 是评级机构对债券发行方偿债能力的评估 - AAA 是高等级,基本不会违约,往下依次是 AA、A、BBB 等 - BBB 及以上是投资级债券,以下叫高收益债券(垃圾债) - 债券的分类和特点 - 按发行方分类 - 国债 - 地方政府债 - 企业债和公司债 - 金融债 - 可转换债 - 利率与债券 - 举例 - 持有一张面值 100 元,票面利率 3% 的 10 年期债券 - 市场利率从 3% 涨到了 4% - 新发行的债券能给投资者 4% 的回报 - 债券必须降价,新买家才能接近 4% 的回报 - 如果市场利率低了,那么债券就抢手 - 如果预期未来利率要下降,现在买长期债券,未来债券价格上涨就赚到了资本利得 - 降息周期中,债券是好投资,不光拿利息,还能赚价差 - 期限越长的债券,对利率变化越敏感 - 加密货币 - 区块链与加密货币 - 区块链要解决的问题 - 不依赖任何中心化机构,让一群互补信任的人也能安全的记账和转账 - 实现方案 - 不把账本交给机构,而是让网络中所有参与者各持有一份完整的账本副本 - 每当发生一笔交易,全网广播,大家一起验证这笔交易是否合法 - 验证通过后,这笔交易会被打包进一个区块,链接到之前的区块后面,形成一条链 - 特点 - 由于每个人都有一份账本,每个区块都包含前一个区块的信息 - 要篡改任何一笔历史记录就必须同时修改全网超过一半的账本,几乎不可能 - 比特币 - 是区块链技术的第一个也是最成功的应用 - 总量上限 2100 万枚,永远不会增发 - 每 4 年产出速度减半 - 任何人都可以参与网络维护(挖矿),维护者获得新产出的比特币作为奖励 - 以太坊与加密生态 - 比特币类似数字黄金,以太坊更像一台全球共享的计算机 - 以太坊的创新在于引入了智能合约,可以在区块链上写程序,这些程序一旦部署自动执行,不需要任何中间人 - 智能合约的方式是把规则和赌金都锁在一段代码里,代码自动从天气数据源获取结果,赢了自动把钱转给赢家,不需要裁判 - DeFi(去中心化金融)用智能合约实现了借贷、保险、交易等传统金融功能 - NFT 是基于区块链的数字所有权证明 - 稳定币(USDT、USDC)是价格锚定美元的加密货币 - 以太坊的代币是 ETH,是使用以太坊网络的燃料,任何人在以太坊上执行智能合约都要支付 ETF 作为手续费 - ETF 的价值和整个以太坊的繁荣程度挂钩 - 风险 - 加密货币极端波动是常态 - 监管不确定性是最大的系统性风险 - 安全风险,交易所被黑客攻击、个人钱包密钥丢失 - 信息不对称 - 没有涨跌停和熔断机制 - 投资者心态 - 市场大多数人在亏钱 - 市场指数长期上涨 - 大多数人亏钱是由于在错误的时间做出了被情绪驱动的决策 - 贪婪与恐惧的循环 - 追涨杀跌,在贪婪的时候高位买入,在恐惧的时候低位卖出 - 常见的心态陷阱 - 频繁交易的冲动 - 过渡关注短期涨跌 - 盲目跟风 - 死扛不止损 - 赚了觉得自己是天才 财务报表与公司分析 - 财务报表与公司分析 - 为什么要看财报 - 财报是什么 - 财务报表就是一家上市公司定期公布的财务数据报告,是公司的体检报告 - 上市公司被法律要求定期披露财报:年报每年一次,最全面最重要 - 年报通常在每年 4 月 30 日之前披露完成 - 财报提供了第一手的、经过审计的、客观的数据 - 三张核心报表速览 - 资产负债表 - 这家公司在某个时间点拥有什么,欠了什么 - 给公司在某一天拍了快照:左边列出所有资产,右边列出所有负债和股东权益 - 资产 = 负债 + 股东权益 - 告诉一家公司家底有多厚,杠杆有多高 - 利润表 - 这家公司在一段时间内赚了多少钱 - 像一段视频,记录了从年初到年末,公司收入多少,花了多少成本和费用、最终净赚多少 - 收入 - 成本 - 费用 = 利润 - 告诉一家公司赚钱能力如何 - 现金流量表 - 这家公司在一段时间内实际收到和花出了多少真金白银 - 利润和现金不是一回事,公司卖出一批货记录 100 万收入和 20 万利润,但是可能没收到客户付款,没有现金 - 现金流量表就是为了区分账面利润和真实现金的 - 告诉一家公司造血能力是否健康 - 在哪里找财报 - 交易所官网 - 上交所上市公司公告页 - 同花顺 APP 个股详情 - 财报看什么 - 主要财务数据和指标(前几页),会汇总最关键的数据,例如营收、净利润、总资产、每股收益等 - 管理层讨论与分析(MD&A)是公司管理层用文字解释这一年经营情况的章节,更容易理解 - 三张核心财务报表的具体数字与附注 - 资产负债表 - 基本结构 - 核心等式:资产=负债+股东权益 - 一家公司拥有的所有东西(资产)要么是借来的(负债),要命是股东投入的或者经营积累的(股东权益) - 假设购买了 300 万房子,首付 100 万是存款,200 万是银行贷款 - 资产 300 万 = 负债 200 万 + 自有权益 100 万 - 资产:公司拥有的现金、厂房、机器设备、存货、别人欠公司的钱 - 负债:公司欠银行的钱、欠供应商的钱、欠员工的工资 - 股东权益:股东投入到本金、历年积累下来没有分掉的利润 - 资产端(变现速度分类) - 流动资产 - 一年内能变成现金的资产,流动性高 - 例如 - 货币基金(公司银行账号里的现金和扣款) - 应收帐款(公司已经把货卖出去了,但是客户没付钱) - 存货(公司仓库里面还没卖出去的产品和原材料) - 预付款项(公司提前付给供应商的钱,还没有收到货) - 非流动性资产 - 需要超过一年才能变现或者公司打算长期持有的资产 - 例如 - 固定资产(厂房、机器设备、办公楼等实物资产) - 无形资产(专利、商标、软件著作权、土地使用权) - 商誉(公司收购另一家公司时多付的溢价) - 长期股权投资(公司持有其他公司的股份) - 负债端(按时间分类) - 流动负债 - 一年内必须偿还的债务 - 例如 - 短期借款(一年内到期的银行贷款) - 应付帐款(公司从供应商那里拿货了,但是没付钱) - 预收帐款(客户提前付给公司,公司还没交货) - 应付职工薪资 - 非流动性负债 - 一年以后需要偿还的长期债务 - 例如 - 长期借款 - 应付债券 - 股东权益 - 股本(股东投入的本金,按面值计算) - 资本公积(股东投入中超过面值的部分,比如 IPO 溢价) - 未分配利润(公司历年赚的钱中没有分红分掉的部分,这是公司内部积累的家底) - 资产负债表的关键视角 - 公司有多少真金白银 - 货币资金:一家公司账上有大量现金,说明财务弹性好,有钱应对突发情况,也有能力抓住新的投资机会 - 如果一家公司常年积累大量现金却不分红也不投入业务,可能说明管理层没有找到好的投资方向,资本配置效率低 - 公司的资产质量如何 - 现金是最优质的资产 - 应收帐款有风险,如果客户赖账不还,应收帐款变成坏账,需要从利润扣除 - 存货:如果是电子产品,存货放久了会贬值或者变为废品 - 如果应收帐款和存货的占比在扩大,远快于营收增速,往往是危险信号 - 钱从哪里来 - 负债和股东权益的结构 - 如果负债占总资产比例很高(>70%),说明公司高度依赖借债经营,一旦经营出现波动,可能出现问题 - 负债率不是越低越好,适度举债可以放大股东的回报率 - 不同行业负债差异很大,银行和房地产天然是高负债行业,科技公司和消费品公司负债率很低 - 衡量偿债能力的指标 - 短期偿债能力 - 看的是公司能不能应付眼前的债务 - 流动比率=流动资产/流动负债 - 衡量的是公司用短期内能变现的资产去覆盖短期内必须偿还的债务的能力 - 一般认为流动比率在 1.5 到 2 之间比较健康,低于 1 意味着短期资产不够偿还短期债务 - 速动比率 - (流动资产-存货)/流动负债 - 把存货提出来,只看更容易变现的资产 - 速动比率在 1 以上通常被认为是安全的,意味着不靠存货就能覆盖短期债务 - 现金比率 - 货币资金/流动负债 - 只看账上最硬的真金白银能覆盖多少短期债务 - 这个指标过于保守,实际中不常用 - 长期偿债能力 - 看的是公司整体的杠杆水平 - 资产负债率=总负债/总资产*100% - 这是最常用的杠杆指标 - 制造业和消费行业资产负债率 40%-60% 算正常,房地产行业 60%-80% 常见 - 科技轻资产公司通常在 30%-50% - 公用事业公司有大量固定资产可能在 50%-70% - 衡量资产质量的指标 - 资产质量指标告诉你的公司家底是否扎实 - 应收帐款周转天数 - 365 / (营业收入/平均应收帐款) - 衡量的是公司从卖出货物到收回现金平均需要多少时间 - 周转天数越短说明公司收钱越快,议价能力越强 - 如果周转天数从去年的 45 天变成了 90 天,说明客户的付款速度明显变慢了 - 要么是公司放松了信用政策,要么是下游客户的经营出了问题,两种都不是好消息 - 存货周转天数 - 365 / (营业成本/平均存货) - 衡量存货从采购到卖出平均需要多少天 - 对于食品、电子产品这类保质期短或者贬值快的行业,存货周期变慢是严重的预警 - 上述两个指标不看绝对值,而是看趋势和同行对比 - 商誉 - 如果一家公司收购另一家公司,出价高于被收购公司净资产的公允价值,多付的被称为商誉 - 商誉本质是对未来协同效应的乐观预期 - 如果收购后实际效果不如预期,商誉会减值 - 商誉会直接从利润扣除,一下让公司从盈利变为亏损 - 当公司资产负债表商誉占比很高,要注意 - 一家好公司的资产负债表 - 账号有充足的资金,不用为短期生存发愁 - 应收帐款和存货的增速与营收增速大致匹配 - 流动比率在 1.5 以上,短期偿债没有压力 - 资产负债率在行业合理范围,没有过度举债 - 商誉占比不高或者没有商誉 - 股东权益中未分配利润持续增长 - 利润表 - 利润表的结构与逻辑 - 利润表记录的是一段时间内公司经营的全过程 - 核心逻辑可以用漏斗来理解:钱从营业收入开始流入,经过一层层的成本和费用扣减,最终剩下的就是净利润 - 营业收入 - 营业收入是起点,也叫营收或者收入,公司通过卖产品或提供服务获得的总金额 - 营收是一家公司规模和市场地位最直观的体现 - 营收不等于公司公司收到的现金,只要货物已交付或者任务已完成,哪怕客户还没付款,会计上也可以确定为收入 - 营业成本 - 公司为了生产产品或提供服务而直接发生的支出 - 例如制造业的原材料采购和工人工资、餐饮业的食材成本 - 毛利润 - 营收减去营业成本就得到毛利润 - 毛利润体现了公司产品本身的盈利能力 - 毛利润没扣除管理层工资、广告费、研发费这些间接支出 - 毛利润可以理解为产品层面赚了多少,后面要扣的费用是公司运营层面花了多少 - 费用 - 销售费用 - 为了把产品卖出去而花的钱 - 例如广告投入、销售人员工资和提成、渠道佣金、促销活动、运输费等 - 管理费用 - 维持公司日常运转的行政支出 - 管理层薪酬、办公室租金、行政人员工资、差旅费、法律和审计费用等 - 研发费用 - 公司投入新产品开发和技术创新的钱 - 营业利润 - 毛利润减去三大费用再加上其他经营收支就得到了营业利润,反映的是公司核心业务的盈利情况 - 营业利润之后还要扣减财务费用、加上投资收益、以及一些营业外收资,最终得到利润总额 - 净利润 - 利润总额扣减掉所得税,就是最终的净利润 - 完整示例 - 营业收入 100 亿,卖出这些产品的直接成本是 55 亿,毛利润是 45 亿 - 为了把产品卖出去花了 15 亿销售费用,公司管理花了 5 亿管理费用,研发新品花了 3 亿研发费用,三项费用合计 23 亿 - 45 亿毛利润减去 23 亿期间费用等于 22 亿营业利润 - 公司有些银行贷款产生了 2 亿利息支出(财务费用),对外投资赚了 1 亿(投资收益),收到 0.5 亿政府补贴(营业外收入) - 利润总额是 22 - 2 + 1 + 0.5 = 21.5 亿 - 按 25% 的企业所得税税率扣税,交了大约 5.4 亿的税,最终净利润是 16.1 亿 - 经常性利润 - 一次性利润:政府补贴是一次性的,明年不一定有 - 利润表关键信号 - 营收增长但利润下降:公司虽然卖的更多了,但是成本费用增长更快,赚钱效率在下降,可能是打价格战或者消费费用失控 - 利润严重依赖非经营性收入:如果投资收益、政府补贴、资产处置收益在利润中占比很大,说明主业赚钱能力可能很弱 - 营收增长异常平滑:可能是人为调节过了 - 某一项费用突然大幅变化 - 核心利润率指标 - 毛利率 - 毛利率 = 毛利润/营业收入*100% - 毛利率最能体现一门生意本质 - 毛利率越高说明产品的附加价值越高,公司在产业链的议价能力越强 - 白酒行业毛利率在 70%-90%,软件和互联网公司毛驴很高,超市零售只有 15%-25%,制造业通常只有 20%-40% - 毛利率不能跨行业比较,但是一个行业内,毛利率高的公司通常意味着产品更有竞争力 - 趋势:如果毛利率下降,但是营收增长,也说明生意在变差 - 净利率 - 净利率 = 净利润/营业收入*100% - 净利率是最终的赚钱效率,体现了公司在扣除所有成本、费用和税后,每 100 块营收最终能留下多少利润 - 净利率综合反映了毛利率和非要红控制两方面的能力,有些公司毛利率很高但是净利率低,说明费用支出太多 - 费用率指标 - 销售费用率 - 销售费用/营业收入*100% - 告诉你公司每赚 100 块钱要花多少在营销推广上 - 消费品公司和互联网公司的销售费用通常较高,有些可能超过 30% - ToB 的工业品或者垄断企业销售费用率可能只有 3%-5% - 销售费用率需要关注的核心问题是效率,花了钱后营收是否在增长,如果营收增速下降了,说明获客效率在下降 - 管理费用率 - 管理费用/营业收入*100% - 反映公司的管理效率 - 随着公司营收增长,管理费用率应该逐步下降,因为管理费用中很多固定支出(办公司租金、行政人员工资) - 营收增长后这些固定支出被摊薄,这就是规模效应 - 如果一家公司营收翻倍但是管理费用上升了,说明管理层没有控制好成本 - 研发费用率 - 研发费用/营业收入*100% - 关键不在于花了多少,而是花出去的钱有没有转化成有竞争力的产品和持续的营收增长 - 通常和营收增速一起看 - 如果研发费用率保持 15% 而营收每年增长 20% 以上,说明研发投入有成果 - 如果研发费用率 20% 但营收已经连续两年不增长说明钱花的不够聪明 - 现金流量表 - 为什么利润不等于现金 - 会计记账采用的是权责发生制,而不是收付实现制 - 只要交易在经济意义上发生,不管钱是否到账,都可以记录为收入或者支出 - 利润表告诉公司应该赚了多少,现金流量表告诉公司实际收到和花出了多少真金白银 - 举例 - 12 月份卖 500 万的货给客户,客户三个月后付款,按权责发生制,12 月利润表就可以确认 500w 利润,挂在应收帐款下 - 12 月买 200 万的设备,能用 10 年,会计上不会把 200 万一次性计入当月成本,而是分 10 年每年折旧 20 万 - 历史上很多公司利润表年年盈利,最后倒闭,就是因为现金流断裂,收不回现金 - 现金流的三大板块 - 经营活动现金流 - 最核心的部分,反映了公司通过日常经营-卖产品,提供服务-实际收到多少现金,花出多出现金 - 流入项包括销售商品收到的现金,提供劳务收到的现金等 - 流出项包括采购原材料支付的现金、支付给员工的现金、缴纳的税款等 - 经营活动现金流净额如果为正,说明公司的业主在造血 - 一家公司可以通过调整会计政策来美化利润,但是经营现金流很难操作,有银行流水佐证 - 通常把经营活动现金流金额和净利润放在一起,如果经营现金流长期大于或接近净利润,说明利润质量很高 - 投资活动现金流 - 反映的是公司在长期资产上的投入和回收 - 流出项主要是购买固定资产、购买无形资产、对外投资收购 - 流入项主要是处置固定资产回收的现金、出售投资回收的现金等 - 通常是负数,一家正在成长的公司需要不断投入资金来扩大产能 - 如果长期为正数,需要担心公司是否是变卖家产来生存 - 筹资活动现金流 - 反映的是公司和资金提供者之间的现金往来 - 流入项包括银行借款、发行债券、增发股票融资等 - 流出项包括偿还借款本金、支付利息、支付股东分红等 - 筹资活动现金流为正说明公司在从外部输血 - 为负说明公司在还血 - 优秀的公司经营现金流充足到不需要外部融资就能支撑投资和分红,筹资活动现金净额为负或者小幅为正 - 通过三类现金流判断公司所处的阶段 - 最健康的组合是经营为正、投资为负、筹资为负 - 意味着公司赚到的现金够多,一边扩张一边还债分红,完全靠自身造血能力驱动发展 - 经营为正、投资为负、筹资为正也很常见,说明公司在快速扩张期 - 经营为负、投资为负、筹资为正是最典型的烧钱模式 - 经营为负、投资为正、筹资为正是最危险的组合 - 主业不赚钱,靠变卖资产和借钱维持运营,是衰退或濒临破产的信号 - 三表之间的关系 - 资产负债表是起点和终点 - 利润表和现金流量表是两条路径,解释了资产负债表从年初状态到年末状态的过程 - 利润表解释了股东权益中未分配利润的变化,现金流量表解释了货币资金的变化 - 三条核心连接线 - 利润表的净利润流入资产负债表的股东权益 - 现金流量表的三类现金流净额合计等于资产负债表上货币资金的变化 - 利润表和现金流量表的桥梁 - 间接法 - 起点是净利润,然后通过一系列调整把净利润还原成经营活动现金流 - 加回折扣和摊销 - 扣除应收帐款的增加 - 扣除存货的增加 - 加回应付帐款的增加 - PE 市盈率 - PE 的定义与直觉理解 - PE = 股价 / 每股收益(EPS) - PE = 总市值 / 净利润 - 假设公司每年都赚一样的钱,需要多少年才能通过利润回本 - 静态 PE 是通过过去一年已经公布的净利润来计算 - 动态 PE 用的是最近四个季度的净利润之和 - PE 的应用方法 - 同行业对比 - 关键不是 PE 的绝对数字,而是差异背后的原因是否合理 - 历史对比 - 同一家公司的 PE 会随着市场情绪波动 - 可以查询到 PE 的历史百分位, - 常见 PE - 银行股 PE 通常在 4-8 倍 - 消费品公司 PE 通常在 20-35 倍 - 高科技成长股 PE 可能在 50 倍甚至 100 倍以上 - PE 局限性 - PE 对亏损公司无效 - 亏损公司的净利润为负数 - PE 可以被一次性因素扭曲 - 政府补贴等一次性因素会带来利润的暴涨或暴跌,PE 大幅失真 - 扣除一次性项目后的扣非净利润算出的 PE 更有参考价值 - 很多网站会提供 PE 和扣非 PE 两个数字 - PE 是静态视角没有考虑增长 - PE 无法反映资产负债情况 - 低 PE 可能是价值陷阱 - 低 PE 如果没有利润稳定或增长的支持就毫无意义 - PB 市净率与 ROE - PB 市净率 - PB = 股价/每股净资产 - PB = 总市值/净资产(股东权益) - 净资产就是资产负债表上的股东权益,也就是总资产减去总负债后真正属于股东的部分 - PB 衡量的是市场愿意为公司的每一块钱净资产支付多少钱 - 直觉含义:今天把这家公司买下来然后立即清算,把所有资产卖掉,把所有债还清,是赚钱还是亏钱 - PB 等于 1 意味着市值刚好等于净资产,以成本价买了公司所有家底 - PB 最适合用于重资产行业的估值 - 银行是 PB 用的最多的领域,银行的资产主要是贷款和债券,账面价值比较接近真实价值 - 中国银行股的 PB 普遍在 0.4-0.8 倍之间,市场担心隐藏的坏账会让净资产缩水 - 房地产、钢铁、建筑等重资产行业也常常用 PB 估值,这些行业净资产是真实可感的硬资产 - PB 不适合轻资产公司 - ROE 净资产收益率 - ROE = 净利润 / 平均净资产 * 100% - ROE 是把利润表和资产负债表连接起来的桥梁,告诉你股东投入的每一块钱在一年能赚回多少利润 - 巴菲特表示如果只能看一个财务指标只会选 ROE - ROE 直接衡量一家公司为股东创造回报的效率,可以理解为股东资金的回报率 - 长期 ROE 能稳定在 15% 以上的公司通常被认为是优秀的企业 - 低于 10% 说明盈利效率一般 - 低于 5% 不如自己买理财产品 - 持续性很关键,连续 5 年甚至 10 年的 ROE 都在 15%-20% 才是优质公司,例如苹果、茅台、腾讯 - ROE、PB、PE 之间的内在关系 - PB = PE x ROE - 如果两家公司的 PE 相同都是 20 倍,一家 ROE 是 25%,另一家是 8% - ROE 高的 PB 是 5 倍,ROE 低的只有 1.6 倍 - 市场愿意为高 ROE 的公司支付更高的 PB 溢价 - 杜邦分析 - 杜邦分析法就是用来拆解 ROE,找到驱动因素的工具 - ROE = 净利润 x 总资产周转率 x 权益系数 - 净利润(营收/总资产)代表赚钱的效率 - 资产周转率(营收/总资产)代表资产的使用效率 - 权益乘数(总资产/股东权益)代表杠杆水平,权益乘数越多说明负债越多 - 茅台这类公司的 ROE 高主要靠超高的净利率 - 沃尔玛这类零售公司的 ROE 主要靠的是极高的资产周转率 - 银行的 ROE 主要来自于高杠杆 - 估值综合应用 - PS 市销率 - PS = 总市值/营业收入 - 衡量的是市场为公司每一块钱的营业收入愿意支付多少钱 - PS 等于 5,意味着你要花 5 块钱才能买到公司 1 块钱的年收入 - PE 对亏损公司无效,因此,PS 就是最实用的估值工具 - PS 的逻辑 - 公司现在不赚钱,但它的收入在高速增长且未来有望实现盈利,市场就会给他的收入一个估值倍速 - 适用场景 - 高增长但是尚未盈利的公司 - 局限性:营收高不代表能赚钱,一旦公司盈利就要切换到 PE - 使用 PS 要关注公司的毛利率,高毛利率加上高营收增速的组合才是 PS 估值的理想标的 - PEG 指标 - PEG = PE / 预期利润增长率(%) - 解决了 PE 最大的缺陷:不考虑增长 - PEG 等于 1 被认为是合理估值,低于 1 被认为是低估,高于 1 被认为是高估 - PEG 最大的局限性就是预期利润增长率用谁的预测,用过去的增速,历史不等于未来 - PEG 的最佳使用场景是盈利且利润增速在 15%-20% 之间的成长股 - 不同类型的公司的估值方法选择 - 稳定盈利的成熟公司用 PE 为主 - 高增长公司用 PEG 为主 - 亏损但高增长的公司用 PS 为主 - 重资产行业用 PB 为主 - 周期性行业需要特殊处理,PE 高买入,PE 低卖出(利润高),更看 PB,跌到 PB 低的时候关注 - 行业分析入门 - 为什么行业分析重要 - 行业分析帮助回答的问题 - 这个行业的蛋糕有多大,还在不在变大 - 这个行业赚钱容不容易、利润会不会被竞争吃掉 - 这个行业未来三到五年会变好还是会变差 - 好行业有几个共同特征 - 市场空间大,还在不断增长 - 行业内公司能赚到不错的利润率 - 龙头公司有明显的竞争壁垒 - 不容易被新技术或者新商业模式颠覆 - 判断方法 - 查看龙头公司的毛利率和净利率趋势

二月 15, 2026

mysql 知识点

环境准备 - 环境配置与校验 - docker-compose 启动 - 命令行连接 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212213500728.png,300,150) - 自检命令 - 版本、用户、数据库 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212213607553.png,330,510) - 字符集 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212213658757.png,200,200) - 时区 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212213740266.png,300,200) - 当前 sql_mode - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212213813122.png,500,100) - 建库建表 - 建库 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212213857582.png,370,88) - 建表 - 插入数据 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212214005617.png,400,300) - 查/改/删 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212214121561.png,500,400) - 导入导出 - 导出(逻辑备份) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212214319634.png,300,35) - 导入 - mysql -h 127.0.0.1 -P 3306 -u root -p demo < demo.sql - 仅导出表结构 - mysqldump -h 127.0.0.1 -P 3306 -u root -p --no-data demo > demo_schema.sql - 账号与权限 - 创建业务账号 (最小权限) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212214524944.png,400,150) - 查看权限 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/12/20260212214555267.png,700,161) 基础查询 - 基础查询 - 电商数据集 - 单表查询 - 查询最近注册的 20 个用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213142935173.png,217,87) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213142955762.png,400,90) - 查询 Tokoyo 的用户列表,按注册时间排序 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213143209509.png,225,89) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213143228515.png,390,68) - 查询 email 以 @test.com 结尾的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213143322457.png,269,71) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213143337869.png,380,38) - 查询注册时间在某区间的用户数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213143454246.png,269,71) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213143510677.png,191,62) - 查询 city 为空的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213143615844.png,179,88) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213143633144.png,788,86) - 查询价格在 [100,300] 的商品,按照 price 排序 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213143725282.png,272,89) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213143739524.png,410,25) - 查询每个 category 下价格最高的商品,并列最高 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213145405332.png,342,225) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213145422430.png,805,127) - 查询 paid/done 订单表,按照 created_at 倒序分页 (page=3, size=10 => offset = 1) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213145716370.png,219,118) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213145729260.png,896,132) - 查询某用户 user_id = 1 最近 10 笔订单 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213145818212.png,217,112) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213145833482.png,899,80) - 查询取消订单 status = 4 中 total_amount > 500 的订单 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213145923736.png,400,390) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213145948655.png,897,55) - 查询订单总额 top20,只统计 paid/done - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213150027449.png,381,107) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213150050228.png,913,202) - 查询今天创建的订单 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213150120558.png,392,110) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213150132173.png,896,84) - 查询最近 30 天内创建但未支付 status = 0 的订单 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213150215201.png,368,114) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213150234416.png,898,80) - 查询商品名称包含 pro 的商品(不区分大小写) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213150419894.png,384,71) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213150435225.png,813,110) - 查询用户表中名称重复的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213150520219.png,210,193) - 聚合统计与报表 - 用户总数、订单总数、已支付订单数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151047000.png,606,92) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151100044.png,628,54) - 每个城市用户数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151129522.png,305,83) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151142263.png,347,162) - 每个 category:商品数、均价、最高、最低 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151216545.png,276,180) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151250131.png,927,136) - 每种订单状态的订单数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151328161.png,306,91) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151340344.png,377,168) - 最近 7 天每天订单数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151501349.png,438,107) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151523622.png,321,173) - 最近 7 天每天 GMV - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151553141.png,474,136) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151607049.png,262,133) - 每个用户订单数 & 总消费 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151654088.png,342,162) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151718808.png,646,126) - 每个用户最近 30 天订单数 & 消费 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151812574.png,370,177) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213151825407.png,711,241) - 订单数 >= 2 的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213155835127.png,356,133) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213155856030.png,394,106) - 累计消费 >= 2000 的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213155928891.png,416,135) - 每个用户客单价 AOV (paid/done) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160054529.png,443,173) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160116877.png,778,135) - 每个 category :销量 & 销售额 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160143067.png,418,203) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160155171.png,527,108) - 每个商品销售额 top20 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160406174.png,404,172) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160420979.png,400,129) - 每个用户买过的不同商品数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160455682.png,463,156) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160508859.png,456,104) - 每个城市 GMV - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160542065.png,344,157) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160553205.png,304,114) - 本月新增注册用户数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160642028.png,611,93) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160654694.png,273,57) - 复购用户数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213160744971.png,295,180) - JOIN 查询 - 订单列表:订单 id、用户 + 订单 + 明细 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213161309112.png,513,195) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213161327058.png,1008,137) - 每个订单:商品件数 & 商品种类数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213161536475.png,560,150) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213161555909.png,697,104) - 每个用户最近一笔订单时间 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213161746033.png,440,86) - 每个用户最近一步订单:订单号 + 金额 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213161834877.png,557,175) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213161848412.png,847,103) - 从未下单的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213161949958.png,390,110) - 从未支付过订单的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213162036222.png,495,113) - 订单总额与明细汇总不一样的订单 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213201928145.png,540,191) - 每个用户最常购买的品类,返回按购买次数排序的 top1,并列会多行 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213202354169.png,502,491) - 每个用户买过的商品清单,去重商品名 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213203446351.png,392,125) - 用户画像,用户 id、城市、订单数、总消费、最近下单时间 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213204110045.png,381,331) - 沉睡用户:注册 > 30 天,且最近 30 天无订单 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213204317323.png,388,175) - 子查询 - 有订单的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213205122672.png,300,300) - 订单金额 > 全站平均订单金额 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213205816927.png,300,300) - 用户消费金额 > 全站平均消费金额 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213205936224.png,453,356) - 每个品类价格最高的商品 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213210041076.png,452,175) - 至少买过 2 个不同品类的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213210149302.png,404,159) - 买过品类 A 但是没有买过品类 B 的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213210244816.png,300,300) - 首单金额 >= 500 的用户 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213210423066.png,581,260) - 存在异常订单的用户(金额不一致) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213210540285.png,546,200) - 用户订单间隔 > 60 天 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213210646528.png,400,300) - 未支付订单占比高的:未支付/总单数 > 0.5 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213210852947.png,600,140) - CASE WHEN 与常用函数 - 订单状态码转中文文案 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213214511165.png,247,286) - 订单金额分层统计 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213214601818.png,396,411) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213214618668.png,572,107) - 用户城市空值归一:NULL/空串 -> UNKNONW,统计城市分布 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213214811983.png,432,200) - 本月到今天 GMV - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213214855944.png,400,200) - 每个用户注册至首单的天数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213215147390.png,492,377) - 提取邮箱并统计各域名用户数 (@ 后面的部分) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213215233832.png,300,100) - 生成脱敏邮箱:前缀保留前 2 位,其余用 ***,再拼回域名 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213215336085.png,367,224) - 最近 7 天每天新增付费用户数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213215604404.png,444,353) - 常见坑点 - NULL 判断不能用 = - COUNT(col) 不会统计 NULL - WHERE 和 HAVING 的过滤时机不同 - LEFT JOIN + WHERE right.col = 会变 left join 为 inner join - IN 包含 NULL 导致 NOT IN 结果异常,建议用 NOT EXISTS - DISTINCT 是对整行去重,不是对某列去重 约束与范式基础 - 约束与范式基础 - 约束 - 目的 - 保证数据一致性 - 让错误尽早暴露 - PRIMARY KEY (主键) - 保证 - 唯一性:表内每行有唯一标识 - 非空性:主键列不能为 NULL - InnoDB 聚簇索引 - UNIQUE (唯一约束) - 保证 - 某列会某族列的值不能重复 - 本质会创建一个唯一索引 - 示例 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/13/20260213220205017.png,450,47) - MySQL 的 UNIQUE 允许多个 NULL,因为 NULL 不等于 NULL - FOREIGN KEY (外键约束,InnoDB 支持,慎用) - 保证 - 子表引用的父键必须存在 - delete/update 时可以配置联动行为 - 优点 - 数据一致性强 - 代价 - 写入路径变重 - 锁和死锁更复杂 - 历史脏数据不好修复 - 跨库不可用 - NOT NULL (非空) - 范式 - 依赖/函数依赖(functional dependency) - 在一张表中,如果确定一组列 X 的值,就能唯一确定另一列 Y 的值,那么 Y 依赖于 X,记作 X -> Y - 目的 - 用一套可检验的规则来设计表结构,使得数据依赖关系放对地方,避免“冗余、不一致、异常操作“三类问题。 - 更新异常 - 同一事实在多处重复,更新要改多行,多处改漏就不一致 - 插入异常 - 插入某类信息,因为表结构耦合,要插入不该填的信息 - 删除异常 - 删除某类记录时,不小心删除本应该保留的数据 - 1NF (原子性) - 列不可再分 - 反例 - user_phone="138,139,140" 多值 - 2NF (消除针对主键的部分依赖) - 针对联合主键的表 - 如果表用 (order_id,product_id) 做联合主键,那么 product_name 不能放在这个表里 - 3NF (消除传递依赖) - 字段不要依赖非主键字段 - 用户表存了 city_id,又存了 cit_name,city_name 依赖 city_id,理论应该拆到 city 表 - 反范式(违反 3NF) - 为了性能/查询便利 - orders 冗余 user_name 快照 - 冗余的是快照字段,而不是事实字段 - 事实字段在主表,订单里的是下单当时的快照 索引 + EXPLAIN - 索引 + EXPLAIN - 准备索引 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214111724844.png,200,50) - 覆盖索引 + 回表 - 聚簇索引 - 在 InnoDB 中,主键索引就是聚簇索引,表的数据行存放在 B+Tree 的叶子节点上,并且物理组织顺序按主键排序 - PRIMARY KEY 不仅仅是纯逻辑约束,还决定了数据的物理布局 - 主键越大、越随机,写入越容易导致页分裂 - 二级索引 - 二级索引 B+Tree 的叶子节点保存的不是整行数据,而是 (user_id, created_at, PRIMARY_KEY_VALUE) - 二级索引带着主键,但是不带其他列 - 回表 - SQL 命中二级索引时 - 先在二级索引树里定位到符合条件的条目 - 拿到这些条目的主键 id - 如果查询需要整行或者非索引列,就需要再用主键去聚簇索引里取行数据,这就是回表 - 时机 - 使用二级索引过滤/排序,但是 SELECT 的列不在索引里,就会回表 - 代价 - 结果行很多时,会变成大量随机 I/O,把一次索引扫描变成大量主键点查 - 覆盖索引 - 如果一个查询所需要的列都包含在某个二级索引里,引擎只读索引即可返回结果,无需回表 - EXPLAIN 查看覆盖索引 - Extra:Using index 表示只用索引就能满足查询列,结合 type/key/rows 判断是否想要查询的覆盖索引 - 实验 - EXPLAIN - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214111914515.png,276,111) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214112042284.png,412,324) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214112157981.png,200,113) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214112214471.png,341,346) - 两条都可能用 key=idx_user_cnt,第一条更容易出现 Extra: Using index (覆盖索引) - 使用 EXPLAIN ANALYZE 看实际的执行效果会更直观 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214140540132.png,337,109) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214140613332.png,1253,85) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214140700653.png,203,107) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214140714419.png,1545,86) - actual time/rows:第二条通常更慢 - loops:多次取行 - 覆盖索引升级:把查询列塞进索引 - idx_user_cnt 也能覆盖 id,因为二级索引叶子也包含主键 id - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214141116222.png,312,107) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214141141310.png,442,320) - 单列索引 vs 联合索引 - 单列索引的劣势 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214141913352.png,251,114) - idx_user 能够快速定位符合条件的集合 - 这些订单在索引里按照 user_id 分组,组内不保证按照 created_at 排序 - 为了排序,需要先取出数据,再做排序 (Using filesort),最后再取前 20 - 联合索引的优势:变排序为索引顺序扫描 - CREATE INDEX idx_user_ct ON orders(user_id, created_at); - 执行逻辑:先定位到 user_id=123 的区间,再在这个区间按照 create_at 顺序向后扫描,扫到 20 条就停 - 联合索引顺序 (a,b,c) - 等值过滤列 (=/IN 选择性高) - 排序列 (ORDER BY/BETWEEN/>=) - 实验 - 清理环境 - SHOW INDEX FROM orders; - ALTER TABLE orders DROP INDEX idx_user_ct; - 只有单列索引 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214143206689.png,383,159) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214143225138.png) - Extra: Using filesort - row:通常偏大 - 加上联合索引 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214151805719.png,487,323) - Extra:filesort 消失,包含 Using index,rows 通常更小 - 最左前缀 + 范围条件 - 最左前缀 - 对于联合索引 (a,b,c),Mysql 可以高效使用 (a) (a,b) (a,b,c) 而不能高效使用 b、c 和 b、c - 等值匹配 vs 范围匹配 - 等值:=、IN(...) - 范围:> >= < <= BETWEEN LIKE 'prefix%' - 联合索引能够向右继续使用下一列,需要前面的列是等值匹配 - 实验 - 命中最左前缀(只用 user_id) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214152949694.png,253,89) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214153221739.png,315,319) - 等值 + 范围 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214153426060.png,368,137) - 只有 created_at (最左前缀缺失) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214154335485.png,156,89) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214154400266.png,376,320) - key = NULL / type = ALL - key_len 判断索引使用了几列 - 联合索引的 key_len 会变小 - 排序优化 (Using filesort 的消除) - Using filesort 含义 - MySQL 不能直接按照索引顺序输出结果,需要额外排序 - 排序可能在内存完成,内存不足才会落盘成临时文件 - 是性能风险信号,排序行数越大越危险 - ORDER BY 能否走索引排序的判定条件 - ORDER BY 的列序必须匹配索引前缀 - 索引是 (a,b),ORDER BY a,ORDER BY a,b 可以满足 - WHERE 的过滤要让排序区间连续 - WHERE a = 常量,ORDER BY b - 排序方向要一致 - ORDER BY created_at DESC - 实验 - 观察 filesort - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214164324417.png,314,107) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214164353039.png,370,350) - 建立匹配索引,消除 filesort - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214164635556.png,303,105) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214164647147.png,412,346) - GroupBy 与临时表 - GROUP BY 策略 - Sorting Aggregation (排序聚合) - 先把数据按 group ky 排序 - 再顺序扫描,连续相同 key 的行聚合 - 如果排序不能通过索引完成,可能出现 Using filesort - Hash aggregation - 用哈希表按 group key 存聚合状态 - 一边扫描一边更新聚合值 - 哈希表大/需要落盘中间结果 -> 出现 Using temporary - Using temporary - 在 Extra 中看到 Using temporary 表示 MySQL 需要创建临时表来保存中间结果。 - 临时表可能存在内存,也可能落盘 - 触发条件 - GROUP BY 的 key 无法直接利用索引顺序 - 同时有 GROUP BY,order by 与 group by 不一致 - 分组结果集很大 - 选择了很多列 - 实验 - 按照用户统计付费订单数与 GMV - 典型报表 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214165615505.png,216,176) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214165637834.png,598,350) - Using temporary 几乎必现 - Using filesort 也很常见 - 函数分组 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214165738573.png,240,112) - DATE(created_at) 对列做函数,会让 idx_ct(created_at) 很难直接用于分组有序输出,通常会扫描大量行 + 临时表 + 排序/哈希 - 使用索引改善 GROUP BY - GROUP BY 很难做到完全不 temporary - 减少扫描行数 (rows) - 让过滤走索引 - 尽量用窄表/覆盖减少回表与临时表宽度 - 索引失效 - 对索引列做函数 - B+Tree 的定位能力被破坏 - B+Tree 索引快是能对原始列值做范围定位 - 写成:WHERE DATE(created_at) = '2026-02-13' 索引里面存的是完整的 create_at 值,对它做了 DATE() 变化,很难精确映射到索引的连续区间 - 反例 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214174327987.png,306,45) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214174352322.png,249,317) - 正例 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214174426808.png,351,68) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214174447994.png,323,321) - 隐式类型转换 - EXPLAIN SELECT * FROM orders WHERE user_id = '123'; - EXPLAIN SELECT * FROM orders WHERE user_id = 123; - LIKE - B+Tree 只能做前缀有界范围 - 'Phone%' 等价一个范围,有连续区间 - '%Pro%' 无法形成连续区间 - OR、NOT、!=、NOT IN - OR 导致优化器放弃单一路径,建议改成 UNION ALL 两段 - !=、NOT 选择性差,扫描大 - EXPLAIN - type (引擎如何找数据),从好到差: - const/system:通过主键或唯一索引直接定位到一行 - ref:用非唯一索引的等值匹配,返回一组行 - range:索引范围扫描,BETWEEN、>=、LIKE - index:全索引扫描,把整棵索引从头扫到尾 - ALL:全表扫描 - key/possible_keys:为什么选择这个索引 - possible_keys:优化器认为可能有用的索引集合 - key:最终选择的索引 - rows/filtered:一眼估算工作量 - rows:优化器估算要读多少行,越大越危险。不是精确值,但是可以判断是否值得优化 - filtered:估算过滤后能留下多少百分比,越小代表过滤越强 - rows * filtered% = 经过 where 过滤后剩余行数 - Extra:性能信号灯(最重要) - Using Where - 表示存储引擎返回行后,SQL 层还需要再做 WHERE 过滤 - 很常见,不一定坏 - 坏的情况:Using where + rows 很大,说明扫很多再过滤 - Using index (覆盖索引) - 表示只用索引就能拿到所有的查询列 - 常用于:只查索引列 + 主键 - 一般是好信号,要结合 Type - ref/range + Using index:常见的高效覆盖 - index + Using index:可能是全索引扫描 - Using filesort(额外排序) - 不利用索引完成 ORDER BY,需要额外排序步骤 - 排序量大可能落盘,速度慢 - Using temporary (临时表) - GROUP BY/DISTINCT/复杂排序 需要临时表存中间结果 - 可能内存,可能落盘(数据大) - Using index condition (ICP 索引条件下推) - 把部分过滤下推到存储引擎层,在扫描索引时就过滤一部分 - 不等于覆盖索引,仍然可能回表 - 索引条件下推 - 把原本回表后需要在 Server 层做的过滤,尽可能提前到存储引擎层,在扫描二级索引时就过滤掉一些不满足条件的记录 - JOIN 与驱动表选择 - MySQL 优化器通常选择“成本更低“的表做驱动表(先过滤更强的) - 驱动表过滤越强越好,被驱动表必须有匹配索引(join key) - JOIN 优化 = 选对驱动表 + 给被驱动表 join key 索引。 - 深分页 OFFSET 优化 - OFFSET 本质:先找到前 1000 行再丢弃掉,成本随着 offset 线性增长 - KeySet:用上次最后一条记录的排序键继续查 - 游标分页 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214204131923.png,522,180) 事务与并发控制 - 事务与并发控制 - ACID - A 原子性:要么全成功,要么全失败,依靠 undo log + 回滚机制 - C 一致性:约束/规则不被破坏 - I 隔离性:并发事务互不干扰到你能接受的程度 - D 持久性:提交后不丢(redo log + 刷盘策略 + 崩溃恢复) - autocommit - autocommit=1 默认 - SELECT @@autocommit - 每一条 DML 语句执行完成都会自动提交 - autocommit=0 - 会话级:需要手动 COMMIT/ROLLBACK - 容易造成忘记提交的长事务 - SET SESSION autocommit = 0; - 一致性视图 - 一致性读 - 普通 SELECT (不带锁子句) 是一致性读 - 不加锁(不阻塞别人写),读到的是某一个一致性快照上的数据 - 好处:读并发极高 - 代价:读到的可能不是最新提交的值 - 锁定读 - SELECT ... FOR UPDATE (排他锁) - SELECT ... LOCK IN SHARE MODE (共享锁) - 会对读到的记录加锁,会阻塞其他事务对这些记录的写 - 实验 - 数据准备 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214210130081.png,345,154) - autocommit=1 下,一条 UPDATE 自动提交 - SessionA - UPDATE tx_demo SET val = val + 1 WHERE id = 1; - SessionB - SELECT val FROM tx_demo WHERE id = 1; - SessionB 立刻看到 + 1 后的值 - 显示事务 + ROLLBACK - SessionA - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214210424078.png,524,120) - SessionB - SELECT val FROM tx_demo WHERE id = 1; - A 在事务内能看到更新后的值 - B 在 A 未提交前看不到变化 - A rollback 后,变化消失,所有会话回到原值 - Commit 的可见性 - Session A - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214210933337.png,395,71) - Session B - SELECT val FROM tx_demo WHERE id = 1; - Commit 后 B 才能看到新值 - 一致性读 + 锁定读 - Session A (不提交) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214211122348.png,425,72) - Session B - UPDATE tx_demo SET val = val + 1 WHERE id = 1; -- 这里会阻塞等待锁 - Session A 把 FOR UPDATE 换成普通 SELECT - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214211244562.png,310,62) - Session B 不会被 A 的普通 SELECT 阻塞 - 普通 SELECT 不会挡住别人的 UPDATE (MVCC) - FOR UPDATE 会挡住别人的UPDATE (行锁) - 隔离级别与三大现象 - 4 级隔离: - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214211712086.png,637,413) - 实验 - 数据准备 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214212001812.png,386,216) - 脏读 - SessionA - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214212132628.png,459,70) - SessionB - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214212335242.png,338,45) - Session A 再次读到 - RU:A 可能读到 999 - RC/RR:A 不会读到 999 - 不可重复读 - 同一事务中,两次读取同一行,结果不同(因为其他事务提交了更新) - SessionA - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214215345504.png,442,71) - SessionB - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214215409512.png,340,62) - SESSIONA 再次读 - RC:A 第二次读到 222 - RR:A 第二次读到 200 - 幻读 - 同一事务,两次按照同一条件查询,返回的行集合不同(因为别的事务插入/删除了符合条件的行) - SessionA - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214215717143.png,450,66) - SessionB - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/14/20260214215745428.png,416,69) - RR 下前后两次读到的都是 2 - RC 下前第一次读到 2,第二次读到了 3 - 锁定读 FOR UPDATE 与 next-key lock - SessionA - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215190602025.png,448,90) - SessionB - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215190620212.png,478,83) - RR + For Update:B 的插入会被阻塞,被 A 的 next-key lock 挡住 - RC 下 FOR Update 的锁范围更小,不一定锁 GAP。 - 结论 - RU 几乎不用:会出现脏读,业务不可接受 - RC 会出现可重复读/幻读,但是并发更松,锁冲突相对较少 - RR 一致性读更稳定:同一事务多次读结果一致 - MVCC(版本链 + Read View) - InnoDB 里面一条记录行在并发更新下会形成版本链 - 当前记录页里保存的是最新版本(Latest Committed 或未提交版本) - 旧版本信息存在 undo log 里面,通过指针串起来(版本链) - 每个版本都有创建它的事务标识:trx_id(以及用于回滚/版本回溯的指针) - Read View:决定你对这个事务能看见哪些版本 - 做快照读时(普通 SELECT),InnoDB 会用一个 Read View 来做可见性判断。 - 对于某条记录的某个版本(由 trx_id 标识): - 如果这个版本是你自己事务写的 -> 可见 - 如果创建该版本的事务在你创建 Read View 时已提交 -> 可见 - 如果创建该版本的事务在你 Read View 时仍未提交(或之后才开始)-> 不可见 -> InnoDB 会沿着 undo 版本回溯,找到一个可见的旧版本。 - RC 与 RR 的关键差异:Read View 的生命周期 - RR:同一事务内的快照读通常复用同一个 Read View -> 两次普通 SELECT 结果一致 - RC:每条语句的快照读都可能生成新的 Read View(语句级一致性)-> 第二次 SELECT 可能看到别的事务提交的新数据 - 快照读 vs 当前读 - 快照读 - 普通 SELECT - 用 Read View + undo 回溯读旧版本 - 通常不加行锁 -> 读不阻塞写,写不阻塞读 - 当前读 - SELECT ... FOR UPDATE - SELECT ... LOCK IN SHARE MODE - UPDATE/DELET - 这些需要读取当前最新且可用于修改/锁定的版本,并对记录/范围加锁 - 当前读不是靠 MVCC 保证一致,是通过锁 - 实验 - 创建表 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215192012594.png,352,155) - RR 下的可重复读 - SessionA - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215192236554.png,449,91) - SessionB - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215192256141.png,355,68) - B 的提交产生了新版本,但是 A 的 ReadView 让它不可见,于是回溯到 v=100 - 当前读会阻塞 (FOR UPDATE) - SessionA - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215192428839.png,453,111) - SessionB - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215192458852.png,525,45) - FORUPDATE 是当前读 + 加锁,阻塞并发写,不走 MVCC 的无锁读 - MVCC 的工程代价 - 长事务会让旧版本无法清理 - 高频更新会制造大量版本 - 行锁结构(Record/Gap/Next-Key) - 锁的位置:索引 - InnoDB 的行锁不是锁表的行号,而是锁索引记录和索引记录之间的间隙 - 三种锁的定义 - Record Lock (记录锁) - 锁住某个具体索引记录 - 典型:按主键/唯一索引等值锁定一行 - 影响:阻塞别人对该记录的 UPDATE/DELETE - Gap Lock(间隙锁) - 锁住索引记录之间的空档范围 - 作用:阻止别人往这个 Gap 里 INSERT 新记录 - 常见于 RR 下的范围锁定读/范围更新 - Next-Key Lock(临建锁) - = Record Lock + Gap Lock - 锁住某条索引记录以及它前面的 Gap - 什么时候出现哪种锁 - 等值命中 主键/唯一索引 - SELECT * FROM t WHERE id = 10 FOR UPDATE; - 通常只需要 Record Lock - 唯一性保证不可能插入另一个 id=10,没必要锁 Gap - 等值命中 非唯一索引 - SELECT * FROM t WHERE k = 10 FOR UPDATE; -- k 是普通索引 - 可能对 k=10 对应的一组索引记录加锁 - 同时可能涉及 next-key/gap 防止插入新的 k=10 造成集合变化 - 范围查询 - SELECT * FROM t WHERE k BETWEEN 10 AND 20 FOR UPDATE; - RR 下通常会对索引 k 的范围加 next-key(覆盖整个区间) - 锁定义语义 - FOR UPDATE(排他锁 X) - 语义:我接下来要修改这些记录,不允许别人并发改/删/锁定 - 会阻塞: - 别人对同一记录的 UPDATE/DELETE - 别人对同一记录的 SELECT ... FOR UPDATE - 在 RR 下,可能阻塞落在同一范围的 INSERT(gap/next-key) - 典型场景 - 扣库存 - 余额扣减 - 状态机推进 - LOCK IN SHARE MODE - 我希望别人不要修改这些记录,但是允许别人也读 - 会阻塞: - 别人对同一记录的 UPDATE/DELETE(写需要 X 锁,与 S 冲突) - 允许别人 SELECT/SELECT ... LOCK IN SHARE MODE - 不建议这样写,建议直接用 FOR UPDATE - 丢失更新与两种解决方案(乐观/悲观) - 丢失更新:两个并发事务基于同一个旧值做读-改-写,后提交的覆盖先提交的写,导致先提交的修改丢失了 - 错误写法 - SELECT stock FROM products WHERE id = 1; 读到 10 - 应用层算出 new_stock = 10 - 3 = 7 - UPDATE products SET stock = 7 WHERE id = 1; - 两个事务同时做,会把其中一个扣减覆盖掉 - 解决方案 1:悲观锁 - 不相信并发环境下别人不会动它,先把行锁住,再读再改写 - 乐观锁 - 我允许并发发生,但提交更新时必须检测数据没人改过,否则失败重试 - 版本号乐观锁(CAS 思路) - 条件更新(无显示 version,适合库存/额度) - 死锁与锁等待排查 - 锁等待 - 表现:某条语句一直卡住(直到超时) - 原因:要的锁被别人持有,只能等 - 结果:最终等到锁释放成功,要么超时失败 - 参数:innodb_lock_wait_timeout,默认 50s - 死锁 - 表现:两(或多)个事务形成循环等待,A 等 B 的锁,B 等 A 的锁 - InnoDB 会主动检测并选择一个事务回滚 - 故障排查 SOP - 用 data_lock_waits 找 blocking_pid - 用 data_locks 看锁类型/索引/是否 GAP - 用 innodb status 还原死锁链路或等待细节 - 找谁挡谁 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215193614734.png,597,223) - waiting_pid:卡住的那条 SQL - blocking_pid:真正的持锁者 - blocking_sql:它在做什么 - 到底等什么的锁 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215193639648.png,336,113) - INDEX_NAME:锁在哪个索引,PRIMARY 还是某个二级索引 - LOCK_MODE:是否包含 GAP - LOCK_STATUS:WAITING/GRANTED - InnoDB Status:死锁/等待的人话版证据 - SHOW ENGINE INNODB STATUS - LATEST DETECTED DEADLOCK:最近一次死锁的完整链路,SQL、索引、锁类型 - TRANSACTIONS:当前锁等待 - Processlist:快速识别 MDL/DDL - SHOW PROCESSLIST; - Waiting for table metadata lock -> 多半是 DDL 或者是长事务 MDL - 实验 - 准备表 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215202416263.png,440,180) - 死锁类型 A:加锁顺序不一致 - SessionA - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215202507865.png,429,93) - SessionB - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215202601314.png,411,87) - 预期:其中一个会话报 deadlock 并回滚 - 下单事务边界设计(订单+明细+库存+支付状态) - 典型下单链路与拆分原则 - 本地事务:创建订单 + 明细 + 预扣/冻结库存 - 事务外:调用支付(或下游) - 本地事务:处理支付回调(幂等)+推进订单状态+扣减冻结/确认库存 - 订单状态机 - 最小状态集 - CREATED:订单已创建 - PENDING_PAYMENT:等待支付 - PAID:支付成功 - CANCELLED:取消/超时取消 - REFUNDED:已退款 - 关键约束:状态推进必须单向并且有条件 - PENDING_PAYMENT -> PAID - PENDING_PAYMENT -> CANCELLED - PAID -> REFUND - 三种库存策略 - 直接扣减 - 下单时直接 stock = stock - n - 风险:支付失败要补偿(回滚库存),需要可靠的取消/超时机制。 - 冻结库存 - 库存分为 - available_stock 可售 - frozen_stock 冻结(锁定给待支付订单) - 下单 available -= n,frozen += n - 支付成功 frozen -= n (确认出库) - 取消/超时:available += n,frozen -= n - 库存流水 - 用流水记录变更,按流水聚合库存 - 适合强一致性审计,但是实现复杂,读性能压力高(需要物化) - 事务边界设计 - 下单:创建订单+明细+冻结库存 - 幂等键:idempotency_key (来自客户端/业务生成),订单表加唯一约束) - 事务内步骤 - 插入订单 - 插入订单明细 - 冻结库存 - 示例 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215205804020.png,593,422) - 调用支付(事务外) - 事务提交后,再调用: - 支付下单 api - 生成支付链接 - 为什么在事务外 - 支付调用可能慢/失败/重试 - 事务内长期持有锁、占连接、阻塞其他订单和库存写,放大锁等待与死锁 - 支付回调(本地事务 2):幂等落库+状态推进+确认库存 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215210021782.png,658,401) - 订单超时取消(本地事务 3):状态推进+释放冻结 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215210059002.png,548,307) - 事务日志(redo/undo/binlog)与两阶段提交(2PC) - redo/undo/binlog - redo:InnoDB,物理页级,WAL - 解决:持久性 + 崩溃恢复 - 记录对数据页做了什么修改,更偏物理意义,用于崩溃后重放到数据页 - 采用 WAL(Write-Ahead Logging):先写日志,再异步刷脏页 - 即使数据页还没刷盘,只要 redo 持久化,崩溃后也能恢复 - 简单理解:数据库修改先写在便签(redo),再慢慢抄到大账本(数据页)。 - undo log:InnoDB,逻辑回滚信息 + 旧版本 - 解决:原子性 + MVCC - 回滚:事务失败时,按 undo 把修改撤销 - MVCC:提供旧版本,让快照读能读到“过去的值“ - 代价:长事务会让 undo 堆积 - binlog(MySQL Server 层,逻辑日志) - 解决:复制(主从)+点时间恢复(PITR)+ 审计 - 记录逻辑事件:如“某表插入了哪些行/执行了什么语句“ - 不属于 InnoDB,而是 MySQL Server 层统一管理 - 主从复制依赖 binlog(从库拉取并重放) - 为什么 redo 不等于 binlog:解决的是不同问题 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215210924663.png,607,263) - 两阶段提交 (2PC):保证 redo 与 binlog 同生共死 - 核心问题:提交时要同时写 redo 和 binlog,如果写到一半崩溃,会出现灾难性不一致 - 只写了 redo,没写 binlog: - 主库崩溃恢复后数据存在,但从库永远看不到(复制丢事务) - 只写了 binlog,没写 redo: - 主库崩溃恢复后数据不存在,但从库重放后出现 - MySQL 用 2PC 把 redo 与 binlog 的提交绑定在一起 - 关键流程 - 事务准备阶段(Prepare) - InnoDB 写 redo log prepare(表示:我已经把这个事务的 redo 记录齐了,处于可提交状态) - 写 binlog - Server 层把该事务写入 binlog(并根据配置决定是否 fsync) - 提交阶段(Commit) - InnoDB 写 redo log commit(表示事务正式提交) - 崩溃恢复判定规则 - 有 prepare 无 commit:看 binlog 是否存在该事务;存在补 commit,否则回滚 - 有 commit:一定提交(重放 redo) - 关键可靠性参数 - innodb_flush_log_at_trx_commit:控制 redo 的刷盘策略 - 1(最安全):每次提交都把 redo 刷盘(fsync) - 2:每次提交写到 OS cache,每秒 fsync(可能丢失 1s 事务) - 0:每秒写 + 刷(风险更大) - sync_binlog:控制 binlog 的刷盘策略 - 1 (最安全):每次提交都 fsync binlog。 - 0:由 OS 决定何时刷(可能丢) - 长事务危害与治理 - 长事务 - 事务持续时间长 - 事务持有锁很久 - 事务一直没有结果 - 危害 - undo 膨胀 + MVCC 历史版本堆积 - 锁等待/吞吐下降 - 死锁概率上升 - MDL 阻塞 DDL/DML - 复制延迟 - 自增锁/热点资源争用加剧 - 备份/一致性快照变慢或变大 - 定位长事务 - 查看当前正在跑的会话 - SHOW PROCESSLIST; - Time 很大 - State 出现 Locked/Waiting for ... lock/Waiting for table metadata lock - 直接找活的最久的事务 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215214600477.png,479,207) - trx_age_sec 最大的 - trx_state 是否 RUNNING/LOCK WAIT - trx_query 在做什么 - 精准定位谁在等谁 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/15/20260215193614734.png,597,223) - 查看锁明细 - SHOW ENGINE INNODB STATUS - 止血:如何让系统恢复 - 让持锁事务提交/回滚 - KILL 持锁连接 - 缩短等待 - 如果是 MDL 卡住,定位 DDL/持锁事务,结束它们,避免高峰 DDL - 制度化 - 应用层硬约束 - 事务内禁止外部调用 - 事务内只做:必要的几条 SQL - 所有事务用 try { ...; commit } catch { rollback } finally { cleanup } - 连接池必须保证 - 出错时 rollback - 归还连接前清理会话状态 - 设置超时 - 业务接口超时 - DB 侧锁等待超时策略 - 批处理/大事务拆分 - 错误示例:UPDATE t SET ... WHERE created_at < '...'; - 按照主键或时间分片,分批提交 - DDL 发布规范 - 高峰期不做 DDL - 选择 online DDL 能力 - 先建索引再切流,或者用灰度策略 - 对 "Waiting for table metadata lock" 设置报警 - 监控报警 - information_schema.innodb_trx 种 trx_age 超阈值数量 - 锁等待数量/时间 - undo 表空间增长 - 复制延迟 表设计与数据治理 - 表设计与数据治理 - 建模方法与边界 - 建模方法 - 列出核心用例 - 查询(读):用户订单列表、订单详情、支付结果、库存可售、待支付超时扫描。 - 命令(写):创建订单、锁/冻结库存、支付回调、取消订单、发货。 - 抽实体与关系 - 订单 - 订单明细 - 库存 - 支付交易/回调 - 事件/审计 - 定边界 - 哪些字段属于状态(可覆盖可更新) - 哪些属于事件/流水(只追加不更新) - 哪些属于派生/统计(可重建,允许异步) - 边界划分 - 状态表 - 特点:一行代表一个业务对象的当前状态,会被更新 - 示例 orders - status、paid_at、cancelled_at 这写会变 - 读多写少,但是必须写正确(条件更新/事务) - 适用于需要当前值的读 - 事件表 - 特点:只追加,不覆盖;用于审计、追溯、重放。 - 示例 order_events - 记录状态变迁:PENDING -> PAID,以及时间、操作者、来源 - 永远不 UPDATE,只 INSERT - 适用 - 查发生过什么 - 重建派生数据 - 流水表 - 特点:和事件表很像,更偏可对账、可汇总 - inventory_ledger、payment_txn - 每次扣减,冻结,释放都记录一条流水 - 可做对账:库存表 = 初始 + 流水汇总 - 适用:资金/库存这种需要强审计的域 - 聚合表 - 特点:为查询加速而存在,可异步更新,可重建 - user_order_summary,daily_gmv - 允许最终一致 - 目标让核心查询不扫大表 - 边界经验 - 状态表保证在线业务 - 时间流水表保证可追溯与对账 - 聚合表保证性能与体验 - 电商“订单-库存-支付“ - 建议最小表集 - orders:订单状态表 - order_iterms:订单明细 1-N - inventory:库存状态表 - payment_notify 或 paymenet_txn:支付回调/交易流水 - order_events:订单事件表 - 边界怎么定 - 订单当前状态只在 orders,不要在多张表复制写 - 所有发生过的动作进 order_events(可追溯) - 支付回调必须有独立表 + 唯一约束做幂等(不要只靠 orders) - 库存推荐冻结模型:inventory.available/frozen,支付成功/取消再结算 - 主键设计 - InnoDB 的数据行物理组织顺序就是主键顺序 - 主键越短越好:二级索引都要携带主键值 - 主键越递增越好:写入更顺序,减少页分裂与随机 I/O - 主键一旦选错很难改 - 主键方案 - 自增 BIGING(最常用,默认推荐) - 雪花/时间有序 ID(推荐的分布式主键) - UUID (不推荐当主键) - 业务号 (一般不建议) - 字段类型与表达 - 字段类型选型 - 金额 - DECIMAL(18,2) - 金额最小货币单位方案:用 BIGINT 存分/厘,但要统一单位与换算 - 对账/财务更偏 DECIMAL - 时间 - DATETIME(3):推荐默认(毫秒精度,语义不依赖死区转换) - TIMESTAMP(3):会受 session 时区影响,范围更小 - 业务事件时间(下单时间、支付时间):优先 DATETIME(3) - 需要跟 UTC/时区联动的场景才考虑 TIMESTAMP - 列表分页建议 ORDER BY created_at DESC, id DESC - 状态/枚举 - TINYINT/SMALLINT 存 code - 代码层映射枚举 - 字符串与文本 - VARCHAR - 适合短文本:订单号、手机号、渠道、外部交易号 - 可建索引、可唯一约束 - TEXT/BLOB - 适合长文本:备注、富文本、日志原文 - TEXT 不能有默认值 - 需要检索的长文本:用搜索引擎倒排,不能指望 MySQL 上做 LIKE 全文 - JSON - 适合 payload/扩展字段 - 不适合高频过滤/排序字段 - JSON 里需要检索的 key 提取成独立列并建索引 - Boolean - MySQL 没有真正 boolean,常用 TINYINT(1) 或者 BIT(1) - 工程上,推荐 TINYINT(1) NOT NULL DEFAULT 0 - NULL 策略 - 建议尽量 NOT NULL - NULL 引入三值逻辑:=,<> 的行为让过滤结果看起来怪 - 统计聚合、唯一约束语义、索引选择也更复杂 - 三值逻辑 - col = NULL 永远为 NULL(不是 true) - 判断 NULL:必须 IS NULL/IS NOT NULL - 字符集与排序规则 - 全库统一 utf8mb4 - 排序规则要统一、可预测 - 表达层面:默认值、精度、约束、生成列 - 默认值 - 时间戳列:created_at/updated_at 建议在应用层写入或触发器默认值策略统一 - 唯一约束与幂等设计 - 唯一约束 - 保证在表内某个键组合只出现一次,是数据层的硬规则,不依赖应用正确性 - 适用:唯一表示、去重、幂等落库的最终防线 - 优点:并发下天然正确 - 幂等 - 保证同一请求执行多次,结果与执行一次等价 - 三要素 - 如何识别同一请求 - 重复请求如何处理 - 副作用怎么避免重复 - 幂等写入的 4 种标准写法 - INSERT ... ON DUPLICATE KEY UPDATE - 第一次插入,第二次则更新某些字段 - INSERT IGNORE - 重复就忽略,不需要更新 - 条件更新(状态机幂等) - 先插流水再改状态 - 软删、审计与可追溯 - 审计字段设计 - created_at DATETIME(3) NOT NULL - updated_at DATETIME(3) NOT NULL - created_by BIGINT NULL (或 NOT NULL + 0) - updated_by BIGINT NULL - created_from VARCHAR(32) NULL (来源:WEB/APP/ADMIN/JOB) - updated_from VARCHAR(32) NULL - 软删除 - 典型软删字段 - is_deleted TINYINT(1) NOT NULL DEFAULT 0 - deleted_at DATETIME(3) NULL - deleted_by BIGINT NULL - delete_reason VARCHAR(64) NULL - 为什么要软删 - 合规与可追溯:能恢复、可审计 - 避免误删不可逆 - 支持撤销/恢复业务 - 状态表 + 事件表 - 仅靠 updated_at 不够,真正追溯需要事件表 - 状态表(当前态) - 事件表(历史轨迹) - order_id - event_type - event_at - operator_id - source - payload - 归档、冷热分离与保留策略 - 归档:把低频/历史数据搬离在线库 - 原因 - 表越来越大 -> 索引越来越大 -> buffer pool 命中下降 -> 查询变慢 - 索引膨胀 -> 写入/更新维护成本变高 -> TPS 下降 - 备份窗口变长、DDL 变更风险大 - 冷数据占用昂贵的 SSD,成本不划算 - 技术手段 - 归档到历史表 - orders(热) + orders_archive(冷) - 分区 - PARTITION BY RANGE (TO_DAYS(created_at)),按月/周分区 - 冷库(另一个 MySQL 实例) - 对象存储/数仓(S3/OSS + Parquet 或 Clickhouse) - 历史明细导出到对象存储,提供离线查询 - 变更与在线 DDL - MDL:MySQL 对表加 Metadata Lock 来保证 DDL 与 DML 的一致性。 运维与排障 - 运维与排障 - 指标体系 - 指标分层 - 业务层(用户感知层) - QPS:每秒 SQL 请求数 - TPS:每秒事务提交数(更接近写负载与提交压力) - RT(P95/P99):请求响应时间分位数:95%/99% 的请求在这个时间内完成。 - 错误率:SQL 执行失败的比例与类型 - 慢查询数量/慢查询占比:超过 long_query_time 的语句数量 (slow log) - 连接与线程层指标 - Threads_connected(当前连接数):当前已建立的客户端连接数量 - Threads_running(正在执行的线程数):正在执行的线程连接数 - max_connections 使用率:Threads_connected/max_connections - InnoDB 引擎层指标 - Buffer Pool Hit Rate(缓冲池命中率):逻辑读有多少比例直接命中内存,而不是去磁盘读页。 - InnoDB 行操作速率(InnoDB 层行读写的频率) - 行锁等待强度:行锁等待次数与等待总时长。 - redo 生成速率 - 脏页比例/flush 压力 - OS 资源层指标 - CPU 与 load - 磁盘 IO - 内存 (swap/page cache) - 网络 体系架构 - mysql 体系架构 - Server 层 - 连接与会话管理 - 客户端连接管理:TCP/Unix Socket,握手认证 - 线程模型:一连接一线程 - 会话上下文:事务隔离级别,临时表,用户变量,prepared statement 等 - SQL 接收与解析 - 词法/语法分析:把 SQL 文本解析成 AST - 预处理:语义检查 - 优化器 - 生成执行计划:选择访问路径、Join 顺序、Join 算法 - 成本估算依赖:统计信息、索引基数、直方图、代价模型 - 典型产物:EXPLAIN 输出 - 执行器 (Executor) - 按执行计划逐步执行 - 调用存储引擎 API 取行,写行 - 负责把结果返回给客户端 - Using filesort、Using temporary 等现象大多发生在执行阶段 - 查询缓存-8.0移除 - 元数据与权限 - Server 日志:binlog(逻辑日志) - binlog 在 Server 层:用于复制(主从)与点时间恢复 - 提交时与 InnoDB redo 通过 2PC 保持一致性 - Storage Engine 层(存储引擎层) - 内存结构 - Buffer Pool:缓存数据页与索引页 - Change Buffer:对二级索引的变更做缓冲 - 磁盘结构 - 表空间:存数据页/索引页 - redo log:WAL,保证崩溃恢复 - undo log:回滚与 MVCC 版本链 - doublewrite buffer:防止部分写导致页损坏 - 索引与访问 - 聚簇索引:PRIMARY KEY 叶子存整行 - 二级索引:叶子存二级键 + 主键值 - 访问路径:范围扫描、点查、回表、覆盖索引 - 事务与 MVCC - 事务隔离:RC/RR/Serializable - MVCC:读视图 + undo 版本链 - 锁:行锁、间隙锁、next-key 锁 - 崩溃恢复 - 依赖 redo:重放已经提交事务对页的修改 - undo:回滚未提交事务 - SELECT 查询典型数据流 - 客户端连接进入 Server 层,会话建立 - Parser 解析 SQL -> AST - Optimizer 基于统计信息选择计划 - Executor 执行计划:通过 Handler 调用 InnoDB 读取行 - InnoDB 在 Buffer Pool 找页 - 结果返回客户端 - 架构图 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/16/20260216200910304.png,978,521) - 事务提交典型数据流 - Executor 调用 InnoDB 写行,更新 buffer pool 中的数据页,产生 undo - 生成 redo WAL,记录页修改 - 提交时 2PC - InnoDB 写 redo prepare - Server 写 binlog - InnoDB 写 redo commit - 后台线程刷脏页到数据文件,不要求提交同步 附录 docker-componse.yml ...

二月 12, 2026

从前序与中序遍历序列构造二叉树

从前序与中序遍历序列构造二叉树 思路 前序确定根,中序切分左右子树 Java 解法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { private int[] preorder; private int[] inorder; private Map<Integer, Integer> inIndex = new HashMap<>(); public TreeNode buildTree(int[] preorder, int[] inorder) { this.preorder = preorder; this.inorder = inorder; for (int i = 0; i < inorder.length; i++) { inIndex.put(inorder[i], i); } return build(0, preorder.length - 1, 0, inorder.length - 1); } private TreeNode build(int preL, int preR, int inL, int inR) { if (preL > preR) { return null; } int rootVal = preorder[preL]; TreeNode root = new TreeNode(rootVal); int k = inIndex.get(root.val); int leftSize = k - inL; root.left = build(preL + 1, preL + leftSize, inL, k + 1); root.right = build(preL + leftSize + 1, preR, k + 1, inR); return root; } }

二月 12, 2026

二叉树展开为链表

二叉树展开为链表 思路 思路一: 维护一个全局指针 prev,表示已经处理好的部分的头节点。 ...

二月 12, 2026

二叉树的右视图

二叉树的右视图 题目 思路 思路 1: 层序遍历,用队列做 BFS,最后出列的就是最右边。 ...

二月 11, 2026

二叉搜索树中第 K 小的元素

题目 思路 思路一. 中序遍历,取第 K 个 思路二. 维护每个子树的大小 ...

二月 11, 2026

酒馆新赛季

Jeef 阿凯最好换技能 养酒馆野猪人很强 鹦鹉必须拿 巨大的火车王很有用 巨大的狂战很有用 低本磁力不贴 二本磁力 + 豆哥额外 Combo 瑞文和点金一起发牌时要额外关注 有鹦鹉的情况下,可以给触发战吼野兽套盾

二月 11, 2026