投资

投资基础认知 - 投资基础认知 - 什么是投资 - 储蓄 - 把钱存起来,追求安全,收益很低 - 保住本金,几乎没有风险,也跑不赢通胀 - 投资 - 将钱投入到能长期产生回报的资产中 - 有逻辑依据,关注长期价值、承担合理风险以获取合理回报 - 投机 - 试图通过短期价格波动赚差价 - 投机接近于赌方向,往往没有对资产本身价值的深入分析,风险很高 - 资产与负债 - 资产 - 能把钱放进你口袋的东西 - 买了一套房,每月收租金 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 低的时候关注 - 行业分析入门 - 为什么行业分析重要 - 行业分析帮助回答的问题 - 这个行业的蛋糕有多大,还在不在变大 - 这个行业赚钱容不容易、利润会不会被竞争吃掉 - 这个行业未来三到五年会变好还是会变差 - 好行业有几个共同特征 - 市场空间大,还在不断增长 - 行业内公司能赚到不错的利润率 - 龙头公司有明显的竞争壁垒 - 不容易被新技术或者新商业模式颠覆 - 判断方法 - 查看龙头公司的毛利率和净利率趋势

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 ...

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

从前序与中序遍历序列构造二叉树 思路 前序确定根,中序切分左右子树 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 瑞文和点金一起发牌时要额外关注 有鹦鹉的情况下,可以给触发战吼野兽套盾

Docker复习

- Docker - mac 安装 -> https://www.docker.com - Container 容器 - 运行代码的隔离环境,是被隔离的进程 - 文件系统被隔离 - 网络被隔离 - 进程空间被限制 - 资源被限制 - 共享宿主机内核 - 启动速度≈启动一个进程(速度快) - 是镜像的运行实例 - 包含内容 - 主进程 - 可写层 - 网络接口 - 挂载点 - 容器应该是单一职责进程 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/05/20260205220658623.png,340,170) - 打开端口 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/05/20260205221048513.png,300,60) - NetWork 网络 - Docker 默认创建虚拟网络 - 容器间可以用容器名互相访问,Docker 自动做服务发现 - Volumn 持久化数据 - volumnes (数据卷) - 是本地文件系统的一个由 docker 管理的位置 - 可以在容器被删除后仍然保留数据 - 容器的默认文件系统是临时、可销毁的,删除容器 = 数据丢失 - 容器内路径 <-> 宿主机目录 - 数据库容器一定要用 volumn - Image 镜像 - 不是压缩包,是只读的文件系统模板 - 包含内容 - 程序 - 依赖 - 运行环境 - 配置 - 是程序快照 - 容器从镜像中创建出来 - 实验 - 拉取并运行第一个容器 - docker run hello-world - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206165323329.png,500,120) - docker 自动执行步骤 - 拉镜像 - 创建容器 - 运行程序 - 退出容器 - 运行一个长期存活的容器 - docker run -it ubuntu bash - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206170740991.png,400,80) - 进入容器 shell - 执行 ps aux - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206171016883.png,100,30) - 仅仅只有两个进程,说明不是完整的 linux 系统 - exit 退出 - 容器的主进程就是 bash, bash 退出,容器就结束 - 容器的生命周期 = 主进程生命周期 - 观察容器生命周期 - created -> running -> stopped -> removed - docker run -d nginx (后台启动 Nginx) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206172849914.png,400,250) - 查看容器 - docker ps - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206172939496.png,400, 50) - 停止容器 - docker stop <container_id> - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206173108853.png,400,150) - 容器仍然存在,只是 stopped - 删除容器 - docker rm <container_id> - 删除容器 vs 删除镜像 - 查看镜像 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206173610615.png,400,150) - 删除镜像 - docker rmi nginx - 容器数据丢失实验 - docker run -it ubuntu bash - echo "hello" > test.txt - exit - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206173816171.png,400,80) - 重新进入 docker run -it ubuntu bash - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206173901539.png,400,80) - 文件消失,因为文件系统是临时层,删除容器后,数据就没了 - 使用 volume 持久化数据 - docker run -it -v mydata:/data ubuntu bash - mydata 是 docker 管理的数据卷,数据存在容器外,容器销毁也不影响 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206174435990.png,400,150) - 容器间通信 - 创建网络 docker network create mynet - 运行数据库 docker run -d --name db --network mynet nginx - 运行另外一个容器 docker run -it --network mynet ubuntu bash - 访问 nginx - apt update - apt install curl - curl db - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206175747288.png,500,200) - dockerfile 部署项目 - 创建项目 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206180229486.png,800,350) - 写一个 Dockerfile - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206201253617.png,400,300) - FROM 指定基础镜像,alpine=极小体积 Linux - WORKDIR /app 相当于 cd /app,后续所有操作都在这个目录,容器内工作目录标准写法 - COPY 把当前目录复制进镜像,左边宿主机,右边容器 - 打包的是代码 + 文件 - EXPOSE 声明端口,但是不开放,真正开放端口在 docker run - CMD 容器启动时执行的命令,容器只运行这个进程 - 构建镜像 - docker build -t my-app . - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206205413049.png,400,300) - 查看镜像 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206205442327.png,400,80) - 运行容器 - docker run -p 3000:3000 my-app - 镜像分层 - Docker 每一行 Dockerfile 都生成一个 layer - 如果 RUN npm install 代码没变,Docker 会复用缓存 - 优化写法 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206205757131.png,400,300) - .dockerignore - 可以不把无关文件复制进镜像 - 停止并删除容器 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206205935054.png,500,100) - 容器是消耗品,应该删了重建,才是 Docker 的正确用法 - multi-stage build - 目标 - 让最终镜像只包含运行所需内容 - 不带编译工具、源码、构建缓存、开发垃圾 - 定义 - 在一个 Dockerfile 里面使用多个 FROM 阶段 - 前面阶段用来构建,最后阶段只保留运行产物 - 拆解 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206210549327.png,500,400) - builder 阶段,专门用来编译、构建、打包 - COPY --from 从 builder 阶段复制文件,只会复制构建产物 - runtime 阶段,最终镜像只包含 dist 运行文件 + node runtime - 国内镜像配置 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206153717824.png,500,200) 容器原理

Redis 复习

- Redis - 环境安装 - docker run -d --name redis -p 6379:6379 redis - docker exec -it redis redis-cli (进入容器安装) - 安装 redis-cli - brew install redis - redis-cli --version - 连接 redis - redis-cli -h 127.0.0.1 -p 6379 - ping - PONG - Redis 定位 - 性能:减少 DB 压力 - 并发:原子操作 - 临时数据:状态、计数、锁 - 不适合场景 - 强一致性核心数据 - 超大对象 - 长期冷数据 - 基本特性 - 命令执行是单线程 - 网络 I/O 是多路复用 - 核心数据结构 - 清空数据 - FLUSHDB - 查看有多少 key - DBSIZE - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206215227359.png,100,35) - String - 基本 GET/SET - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206215323487.png,130,35) - 带过期时间 TTL - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206215527894.png,130,80) - EX 是秒,PX 是毫秒 - TTL 返回剩余的秒数 - TTL = -1: 存在但没过期时间,-2 不存在 - 原子自增(计数器) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206215723437.png,130,90) - List - 左进右出:队列 FIFO - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206215909155.png,130,150) - 阻塞队列 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206220103319.png,640,54) - BRPOP key timeout - key 要监听的队列 - timeout 最大等待时间(s) 0=无限等待,5=最多等5s - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206220135317.png,640,54) - Hash (存对象) - 写入写出 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/06/20260206220545889.png,500,420) - Set (去重,关系,共同好友) - 去重集合 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207000117913.png,400,200) - 交集、并集、差集 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207000306600.png,400,200) - ZSet (排行榜:积分、热度、TopN) - ZSet = Set + Score - 添加与排序读取 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207000515210.png,420,370) - 增加分数与查询名次 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207142718618.png,320,110) - TOPN - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207142916620.png,200,40) - 按成员删除 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210171017828.png,360,94) - 按 score 范围删除 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210171755055.png,377,128) - 按排名删除 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210172016602.png,466,343) - Bitmap (用户签到/布尔状态) - 场景:统计 2026-02 月的签到情况 - 思路 - 一个用户 = 1 个 Bitmap - 一天 = 1 bit - 签到 = 1 - 未签到 = 0 - key = sign:用户 ID:202602 - offset = 第几天 - 1 - 基础实验 - 第一天签到 + 第二天签到 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210164227735.png,353,78) - 查询某天是否签到 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210164101061.png,330,37) - 统计本月签到天数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210164306694.png,342,39) - HyperLogLog (UV/去重统计) - 场景:统计某天网站 UV - 模拟用户访问 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210164510486.png,323,117) - 统计 UV - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210164530550.png,294,36) - 特点 - 内存:固定 12KB - 准确度:99% - Geo (地理位置/附近的人) - 场景 - 查找 5km 内的商家 - ZSet + GeoHash - 添加地理位置 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210165000620.png,441,116) - 查看两点距离 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210165107321.png,441,50) - 查询附近 1500km 的城市 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210165141208.png,431,58) - 带距离排序 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210165203816.png,431,110) - 通用排查命令 - 查看 Key - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207143038160.png,100,23) - 一般生产环境中不建议用 KEYS,建议使用 SCAN - 查看类型/是否存在 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207143201412.png,100,40) - 删除 key - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207143312380.png,100,22) - 缓存系统实战 - 目标 - 实现用户查询接口: Get /user/{id} - 架构 - Controller -> Service -> Redis -> FakeDB - 逻辑 - 先查 Redis - 没命中查数据库 - 写回 Redis - 加 TTL - 防穿透 + 防击穿 - Java 实现 - 初始化项目 https://start.spring.io - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207144538747.png,240,290) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207145053224.png,100,22) - 配置 RedisTemplate - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207162941344.png,420,200) - 编写 Service - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207164953341.png,620,900) - 编写 Controller - 访问 localhost:8080/user/1 - 查看 redis 内容 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207163242027.png,200,30) - 压测 - ab -n 10000 -c 100 http://localhost:8080/user/1 - 秒杀系统实战 - 项目结构 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207202201922.png,180,560) - 库存扣减 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207202254845.png,500,500) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/07/20260207202428795.png,400,190) - 消息队列 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208103843531.png,600,200) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208103947825.png,200,400) - 分布式锁 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208104108604.png,600,660) - 限流 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208104251769.png,600,300) - service - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208104346930.png,200,300) - controller - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208104412950.png,200,250) - 压测测试 - ab -n 1000 -c 200 http://localhost:8080/seckill/1 - 进阶 - 滑动窗口限流 - lua - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208174051889.png,400,600) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208174120075.png,400,120) - service - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208174212781.png,250,300) - 令牌桶 - lua - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208183418934.png,200,600) - 持久化 - RDB(快照) - 机制:周期性生成内存快照文件 dump.rdb - 优点:恢复快、文件紧凑 - 缺点:可能丢失最近一次快照之后的数据 - 关键点:fork 触发 COW,会带来短暂的延迟和额外内存占用 - AOF(追加日志) - 机制:写命令追加到 appendonly.aof - 刷盘策略 - appendfsync always:每条都写 fsync,最安全最慢 - everysec:每秒 fsync,最常用,最多丢 1s - no:交给 OS,风险高 - AOF 重写:压缩日志,避免无限膨胀 - 混合持久化 - AOF 前半段是 RDB 快照,后半段是增量 AOF - 实验一:只开 RDB,模拟宕机恢复 - 只启用 RDB 快照 - 编写配置文件 redis-rdb.conf - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208204018291.png,300,200) - 启动 redis 容器 - mkdir -p ./redis-data - docker rm -f redis-rdb 2>/dev/null - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208212630821.png,300,100) - 验证 docker logs redis-rdb --tail 20 - 进入容器 - docker exec -it redis-rdb redis-cli - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208212952482.png,200,320) - 写入大量数据 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208213207221.png,200,40) - 观察 persistence - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208213348306.png,300,620) - 手动触发 BGSAVE - 制造快照后的新增数据(丢失窗口) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208213600877.png,300,30) - 强制宕机 - docker kill -s KILL redis-rdb - 重启 redis - docker start redis-rdb - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208213747156.png,320,270) - 验证 rdb 文件 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208213916814.png,200,50) - 实验 2:只开 AOF,验证刷盘策略差异 - 清理旧容器和目录 - docker rm -f redis-aof 2>/dev/null - rm -rf ./redis-aof-data - 准备三份配置 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208220151934.png,320,590) - 清空数据&启动 redis - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208220813971.png,550,130) - 检查 AOF 开启 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208221351700.png,570,220) - 写入负载 + 强制关闭 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208221604525.png,150,40) - docker kill -s KILL redis-aof - 重启 Redis - docker start redis-aof - 查看计数 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208221709710.png) - 实验 3:AOF rewrite 触发与观测 - auto-aof-rewrite-percentage 100 - auto-aof-rewrite-min-size 64mb - 主从复制 - 目的:读扩展、数据冗余、高可用 - 全量复制:第一次同步或者 backlog 不够时 - 增量复制(PSYNC):短线重连后从 backlog 补差异 - 实验 1:搭主从并验证数据一致 - 启动 Master - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208225457359.png,200,40) - 启动 Replica - 创建并加入网络 - docker network create redis-net 2>/dev/null - docker network connect redis-net redis-master - 启动 replica 并加入网络 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208232745496.png,250,50) - 查看复制状态 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208233441611.png,400,400) - 验证数据一致性 - 主库写入 + 从库读取 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208233706250.png,330,260) - 验证从库默认只读 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208233757628.png,300,70) - 查看复制延迟 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/08/20260208233852945.png,100,20) - 排障 INFO replication - 哨兵 Sentinel (高可用) - 目的:监控、选主、故障转移、通知客户端 - 核心:主观下线、客观下线、选举与 failover - 客户端连接地址:不用写死 master 地址,而是通过哨兵获取当前 master - 实验:搭建 1 主 2 从+ 3 Sentinel - 准备目录 - mkdir -p redis-sentinel-lab/{conf,data} - 准备 Redis 配置 - master 配置 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209102606425.png,200,90) - replica 配置 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209102646643.png,150,120) - sentinel 配置 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209201652255.png,500,400) - quorum 2:3 个 sentinel 里至少 2 个认为 master 有问题,才会进入客观下线/转移流程 - down-after-milliseconds:5s 没有响应就 SDOWN。 - docker-compose 启动 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209103212388.png,400,220) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209103505779.png,200,150) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209103544585.png,500,40) - 验证主从复制 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209103652649.png,350,100) - redis-cli -h 127.0.0.1 -p 6379 -a 123456 INFO replication - redis-cli -h 127.0.0.1 -p 6380 -a 123456 INFO replication - redis-cli -h 127.0.0.1 -p 6381 -a 123456 INFO replication - 验证写主从一致性 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209103756350.png,350,100) - 验证 Sentinel 监控是否生效 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209201810208.png,200,800) - 查看主库信息 redis-cli -p 26379 SENTINEL master mymaster - 查看从库信息 redis-cli -p 26379 SENTINEL slaves mymaster - 实验:故障演练 - 写入测试数据,验证切主后数据仍然可写 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209202049854.png,400,150) - 观察主从拓扑 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209202140865.png,900,300) - 查看 Sentinel 实时日志 - docker compose logs -f --tail 200 sentinel-1 - 模拟主库宕机 - docker kill -s KILL $(docker compose ps -q redis-master) - 等待故障转移完成(5-15s) - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209214118816.png,500,50) - 确定新 master - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209214241750.png,600,30) - 验证其中一个哪个变成 master - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209214340957.png,750,100) - 验证切主后仍然可写 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209214638757.png,700,140) - 启动旧 master - docker compose up -d redis-master - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209214737010.png,400,50) - 旧 master 变成 slave - 最终一致性验证 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209214827689.png,350,70) - 实验:SpringBoot 无感切主 - 验证 Sentiel 当前 master - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209220539046.png,600,70) - 配置 Sentinel 连接 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209224129500.png,515,227) - 编写 Controller 验证读写 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209221005722.png,438,412) - 构建镜像 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209222203602.png,370,480) - docker build -t redis-demo . - 切主前写入 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/09/20260209224307958.png,300,80) - 模拟主库宕机 - docker kill -s KILL $(docker compose ps -q redis-master) - 观察 sentinel 选主 - redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster - 内存管理 - INFO memory - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210104614321.png,438,172) - used_memory: Redis 实际分配的内存 - used_memory_human:人类可读的内粗 - used_memory_rss:进程在 OS 视角占用的物理内存 - used_memory_peak:历史峰值 - mem_fragmentation:碎片比 = rss/used_memory >= 2 碎片偏高 - 上限相关 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210105023351.png,250,40) - maxmemory:代表你给 Redis 的硬上限,等于 0 代表未设置内存上限,Redis 会继续申请内存知道吃光资源,不会触发淘汰侧路 - maxmemory-policy:代表 Redis 达到上限时怎么处理 - 淘汰策略 - allkeys-lfu (强烈推荐,缓存业务首选) - 所有 key 都可能被淘汰 - 适合热点明显的缓存 - allkeys-lru - 适合访问模式更均匀,有最近性的场景 - volatile-ttl - 只淘汰带过期时间的 key - noeviction - 拒绝写入,适合强一致性 - 实验:设置 maxmemory + 不同淘汰策略对比 - 设置较小的 maxmemory - redis-cli -a 123456 CONFIG SET maxmemory 10mb - 测试策略 - redis-cli -a 123456 FLUSHDB - redis-cli -a 123456 CONFIG SET maxmemory-policy noeviction - 持续写入大 value - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210123915656.png,700,200) - 观察指标 - redis-cli -a 123456 INFO stats | egrep "evicted_keys|keyspace_hits|keyspace_misses" - redis-cli -a 123456 INFO memory | egrep "used_memory_human|maxmemory_human|mem_fragmentation_ratio" - 实验:LFU vs LRU 对热点的保护效果 - 目标:构造一个热点 key 与大量冷 key,验证 LFU 更能保护热点 - 策略设置 - redis-cli -a 123456 CONFIG SET maxmemory-policy allkeys-lfu - 写入热点 key - redis-cli -a 123456 SET hot "1" - 增加访问频率 - for i in $(seq 1 20000); do redis-cli -a 123456 GET hot > /dev/null; done - 写入大量冷 key - 检查 hot 是否存在 - 实验:内存碎片 fragmentation 观察与整理 - 制造碎片 - 反复创建/删除很多不同大小的 value - Hash/List 不断增长、缩小 - 缓解手段 - 避免频繁大幅变更 value 大小 - 拆分 bigkey - 合理设置 maxmemory,预留空间 - Redis 4+ 可以考虑 memory purge - 实验:BigKey 检测与治理 - 构造 bigkey - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210125423939.png,785,132) - 查看内存 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210125448129.png,425,226) - 粗定位 --bigkeys (抽样/扫描) - 看到按类型统计的最大 key - 会扫描 keyspace,生产慎用 - ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/10/20260210160144500.png,700,400) - 精确测量 MEMORY USAGE - redis-cli -a 123456 MEMORY USAGE big:hash - 观察 BigKey 对延迟的影响 - 打开延迟监控 - redis-cli -a 123456 CONFIG SET latency-monitor-threshold 10 - 删除 bigkey - 查看延迟事件 - redis-cli -a 123456 LATENCY LATEST - 危害 - 阻塞主线程 - 复制/持久化压力 - 网络抖动 - 治理方案 - 拆分设计 附录 缓存系统实战 缓存系统实战 application.properties: ...

二叉树的层序遍历

二叉树的层序遍历 题目 思路 put root into a queue queue is not empty 2.1. size = queue.size(), this is exactly how many nodes are in the current level. 2.1. repeat size times: * pop one node from queue * add its value to level * add left and right child 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 class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res = new ArrayList<>(); if (root == null) { return res; } Deque<TreeNode> q = new ArrayDeque<>(); q.offer(root); while(!q.isEmpty()) { int size = q.size(); List<Integer> level = new ArrayList<>(size); for (int i = 0; i < size; i ++) { TreeNode node = q.poll(); level.add(node.val); if (node.left != null) q.offer(node.left); if (node.right != null) q.offer(node.right); } res.add(level); } return res; } }

二月 6, 2026