跳至主要内容

博文

MySQL EXPLAIN 怎么看

MySQL EXPLAIN 怎么看 EXPLAIN 输出主要看这几个关键字段,我按重要性和常见坑点来讲。 核心字段速览 字段 含义 id 查询的执行顺序标识,id 越大越先执行(子查询、派生表会有不同 id) select_type 查询类型:SIMPLE、PRIMARY、SUBQUERY、DERIVED、UNION 等 table 当前这一行对应的表 type 最重要 ,访问类型,反映查询效率 possible_keys 可能用到的索引 key 实际用到 的索引 key_len 索引使用的字节数,可以判断联合索引用了几列 rows 预估扫描行数(估算值,非精确) filtered 按条件过滤后剩余行的百分比 Extra 第二重要 ,很多关键信息都在这里 type 字段:从好到差的排序 system > const > eq_ref > ref > range > index > ALL system/const :主键或唯一索引等值查询,最快,基本只在单表单行时出现 eq_ref :多表连接时,被驱动表用主键/唯一索引做等值匹配(比如 a.id = b.a_id 且 b.a_id 是唯一索引) ref :普通索引的等值查询,可能返回多行 range :索引范围扫描,比如 > 、 < 、 BETWEEN 、 IN index :索引全扫描(遍历整棵索引树),比 ALL 好但依然要扫全部索引 ALL :全表扫描,一般需要优化 经验标准 :一般要求至少达到 range 级别,最好是 ref 或以上,看到大表出现 ALL 基本就要警觉了。 Extra 字段:常见情况解读 1. Using filesort(需要额外排序) 出现在 ORDER BY 无法利用索引顺序时,MySQL 需要在内存或磁盘中额外排序。 常见诱因 : - ORDER BY 的列没有索引 - 联合索引使用顺序和 ORDER BY 不一致 - WHERE 条件用了索引,但排序列不在索引里,且中间断了顺序性 优化思路 :建立能同时覆盖 WHERE 和 ORDER BY 的联合索引,让索引顺序和排序顺序一致。 2...
最新博文

优惠券规则引擎算法核心

规则引擎的"算法"其实不是一个单一算法,而是三个简单机制拼起来的: 一、执行模型:责任链 + 短路求值 规则不是把所有维度一次性打分再综合判断,而是按类型(领取/门槛/叠加/计价)分成一条条链,链上的规则按优先级排好顺序,从头开始逐条求值。只要有一条不通过,立刻停止,后面的规则根本不会被执行。这样做两个好处:一是省计算,大部分场景第一条不通过的规则往往排在前面就能拦住;二是"因为哪条规则被拒绝"天然有唯一确定的答案,不会出现好几条规则都不通过、不知道该给用户看哪个原因的情况。 二、表达模型:统一接口 + 两种求值方式 所有规则背后都实现同一个"输入上下文、输出通过与否"的接口,但内部求值方式不同。结构化规则(互斥组、数量上限)本质是直接在代码里写好的逻辑判断,输入什么类型就按什么逻辑走。表达式规则则是运行时的一个小型解释器:配置的字符串在编译阶段被解析成一棵语法树,求值时对着这棵树递归计算,碰到"订单金额""用户等级"这类字段引用,就去当前上下文里取对应的值代进去。因为两者对外都是同一个接口,链上可以随意混排,规则引擎本身不需要关心某条规则到底是硬编码的还是配置出来的。 三、一致性模型:整体编译 + 原子替换 规则变更不是"改一条生效一条",而是把这一批新规则 整体 重新编译成一份全新的、不可变的规则集合,编译全部成功才用一个原子指针把"当前生效的规则集合"整体切换过去。切换的一瞬间,正在处理中的老请求仍然用切换前的那份规则跑完,不会出现跑到一半规则变了的中间状态;读取规则的一方也完全不需要加锁,只是读一个指针。这本质上是一种"整体重建、原子替换"的无锁方案,好处是新规则里哪怕只有一条写错导致编译失败,也是整批回退,不会出现新旧规则混杂生效的情况。 四、冲突检测:集合求交 规则之间是否矛盾,判断方式很朴素——把每个券模板所在的互斥组做成一个"模板 → 它所属的组"的索引,两个模板是否互斥,就看它们各自所在的组集合有没有交集;而"必须同时使用"和"不能同时使用"这两组模板 ID,如果本身就有重叠,那就是配置自相矛盾,直接在提交时拦截,不...

go编译参数:race,trace和逃逸分析

可以把 Go 这三个东西理解成分别回答三个完全不同的问题: 工具 核心问题 关注对象 常用命令 Race 「有没有多个 goroutine 在同时乱改数据?」 并发内存访问 go test -race ./... Trace 「程序运行时到底发生了什么?」 Goroutine、调度、GC、阻塞 go test -trace=trace.out 逃逸分析 「这个变量为什么跑到堆上去了?」 内存分配 go build -gcflags="-m" 1. Race:查并发 Bug var n int go func () { n++ }() go func () { n++ }() Race Detector 会告诉你: Goroutine A 正在写 n Goroutine B 同时也在读/写 n ↓ DATA RACE 它关注的是 内存访问之间有没有正确同步 。 2. Trace:查运行时行为 例如你的服务突然: CPU 很高 ↓ 到底是哪个 goroutine 在跑? ↓ 是不是 GC? 是不是锁阻塞? 是不是 syscall? 是不是网络等待? Trace 会把运行过程记录下来,然后: go tool trace trace.out 你可以看到 Goroutine 的运行、阻塞、唤醒,以及 GC、调度器等信息。 所以它更像: 给 Go 程序装一个“黑匣子”,看看程序运行过程中到底发生了什么。 3. 逃逸分析:查内存分配 func foo () * int { x := 10 return &x } 编译器发现: x escapes to heap 因为 foo() 返回以后 x 还必须存在。 另一个例子: func foo () { x := 10 fmt.Println(x) } 编译器可能判断 x 不需要逃逸到堆。 它主要回答: 这个对象能不能安全地留在栈上?如果不能,就放堆上。 最简单的记忆方式 Race ↓ 并发有没有撞车? 🚗💥 Trace ↓ 程序运行时发生了什么? 🔍 Escape Analysis ↓ 变量到底该放栈还...

Go编写etcd注册中心Demo

可以。用 etcd 做注册中心,核心其实就三件事: 服务启动 → 注册实例 服务存活 → 续租(KeepAlive) 服务下线/租约过期 → etcd 自动删除实例,消费者通过 Watch 感知 下面给你一个尽量接近真实微服务场景、但保持最小化的 Go Demo。 1. 安装依赖 go get go.etcd.io/etcd/client/v3 假设本地 etcd: 127.0.0.1:2379 2. 服务注册 package main import ( "context" "fmt" "log" "time" clientv3 "go.etcd.io/etcd/client/v3" ) func main () { cli, err := clientv3.New(clientv3.Config{ Endpoints: [] string { "127.0.0.1:2379" }, DialTimeout: 5 * time.Second, }) if err != nil { log.Fatal(err) } defer cli.Close() // 创建 10 秒租约 leaseResp, err := cli.Grant(context.Background(), 10 ) if err != nil { log.Fatal(err) } // 注册服务 key := "/services/order/192.168.1.10:8080" _, err = cli.Put( context.Background(), key, `{"host":"192.168.1.10","port":8080}` , clientv3.W...

从 Gin + GORM 到 go-zero:我把配置、代码生成、RPC、缓存、日志、链路追踪和熔断串成了一个可运行 Demo

如果你已经熟悉 Gin 路由、GORM 查库,也听过 go-zero 的大名,那么这篇文章就是写给你的。 很多介绍 go-zero 的文章都会说: 它有代码生成 它内置微服务治理 它支持 zRPC 它有缓存、限流、熔断、链路追踪 它比 Gin + GORM 更工程化 但真正上手时,很多人还是会卡在一堆细节上: .api 文件怎么写? goctl 怎么生成项目? 配置怎么写? MySQL 和 Redis 怎么接? gRPC 服务怎么起? API 网关怎么调 RPC? 链路追踪怎么上报? 超时到底怎么传导? 熔断到底要不要自己配? 所以这篇文章不准备继续讲概念,而是直接带你串一个 能跑通的最小完整 Demo 。 读完并跑通之后,你会得到一个包含以下能力的 go-zero 项目: REST 路由: /ping 、 /api/order/:id gRPC 端点: user.User/GetUser MySQL 数据访问 Redis 缓存 日志输出 链路追踪上报 超时链路传导 内置熔断能力 goctl 自动生成 API / RPC / Model 也就是说,这篇文章不是“go-zero 是什么”,而是: 怎么把 go-zero 真正跑成一个可用工程。 一、为什么从 Gin + GORM 走向 go-zero? 如果你用 Gin + GORM 写过项目,一定很熟悉这种模式: 用 Gin 写路由和 Handler 用 GORM 做数据库访问 用 Viper 读配置 用中间件处理登录鉴权 用 Zap 或 Logrus 打日志 自己封装 HTTP Client 调别的系统 自己接 Jaeger、Consul、Prometheus、限流器、熔断器 这套方案很灵活,但问题也很明显: 所有稳定性能力都要自己拼。 比如: 超时传递 服务发现 熔断 限流 降载 链路追踪 日志规范 错误处理 工程目录规范 每一个都要自己选型、封装、踩坑。项目大了以后,团队协作成本会越来越高。 go-zero 的价值,不是取代 Gin,而是把这些工程能力尽量内置化、标准化。 一句话概括: Gin + GORM 给你自由,go-zero 给你“少踩坑的自由”。 二、这个 Dem...

Go-zero框架深度使用教程

Go-Zero 深度实战教程:从 Gin + GORM 出发,掌握微服务框架 适合人群:已掌握 Go 基础语法、熟悉 Gin 路由/中间件写法、用过 GORM 做数据访问的开发者。 目标:不是重新学一门语言,而是理解 go-zero 多解决了什么问题 ,以及它的 工程化套路 该怎么落地。 目录 为什么从 Gin + GORM 走向 go-zero 核心概念对照表(先建立心智模型) 环境搭建与 goctl 工具链 API 服务实战:从 .api 文件到可运行服务 数据层:goctl model 对比 GORM,以及如何共存 配置管理:yaml + conf.MustLoad 中间件系统对比 深度特性(这是 go-zero 真正的护城河) 微服务实战:网关 + RPC 服务打通 从 Gin 单体迁移到 go-zero 的建议路径 常用命令速查 & 学习资源 一、为什么从 Gin + GORM 走向 go-zero Gin 是一个 极简 HTTP 路由框架 ,GORM 是一个 ORM ,两者组合起来非常灵活,但灵活的代价是:熔断、限流、降载、超时传递、服务发现、链路追踪……这些"稳定性工程"能力都要自己拼凑第三方库(比如 sony/gobreaker 、 golang.org/x/time/rate 、 consul 、 jaeger 客户端等),风格不统一,团队协作成本高。 go-zero 的定位不是取代 Gin ,而是在 Gin 解决的"路由 + Handler"问题之上,把一整套微服务治理能力 内置化、标准化 ,并用代码生成工具 goctl 把"写业务代码"和"写工程样板代码"彻底分离。 一句话总结定位差异: Gin + GORM go-zero 定位 HTTP 路由库 + ORM 库 全链路微服务框架(API 网关 + RPC + 治理) 设计哲学 你自己决定怎么组织工程 约定优于配置,失败驱动编程(Failure-oriented Programming) 代码生成 无(或自己写脚手架) goctl 一键生成 API/RPC/Model 骨架 稳定性能力 需要自己集成三...

Go语言职位所需技能要求

Golang 后端开发(B端/微服务方向)面试技术指导文档 适用岗位画像:Go微服务 + gRPC/Protobuf + MySQL/Redis/Kafka + 复杂B端业务(订单/供应链/ERP)+ 系统对接 本文档假设你已熟悉Go语言本身,重点补齐 架构思维 和 中间件深度 两块短板。 目录 gRPC / Protobuf 实战 go-zero / gin 微服务框架分层 MySQL 性能调优功底 Redis 深度使用 Kafka 消息可靠性 B端业务建模能力(订单状态机 / 事件驱动) 系统对接与集成经验 AI工程化经验 模拟面试问题清单 建议学习路径与时间分配 一、gRPC / Protobuf 实战 这是本岗位 第一优先级 ,因为职责里明确写了"完成gRPC/Proto接口开发与联调"。面试官大概率会让你现场画一个服务调用流程,或者问"如果下游服务超时了你怎么处理"。 1.1 Proto文件设计能力 你需要能独立写出一份规范的 .proto 文件,不只是"看得懂": syntax = "proto3"; package order.v1; option go_package = "your-module/pb/order"; // 订单服务 service OrderService { rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse); rpc GetOrder(GetOrderRequest) returns (Order); // 流式接口:批量查询场景常用 rpc ListOrders(ListOrdersRequest) returns (stream Order); } message CreateOrderRequest { string customer_id = 1; repeated OrderItem items = 2; string idempotency_key = 3; // 幂等键,面试常考点 } message OrderItem { string produ...