招银云创后端二面:慢查询与重复支付场景题
- 轮次
- 二面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- ArrayList 和 LinkedList 的区别?讲一下你的理解,什么时候用哪个?
- 现在银行卡查询用「卡号 + 日期」去查非常慢,你怎么排查慢的原因?假如已经对卡号和日期建了索引,用
where 交易日期 < 当前日期查询,效率怎么样? - 场景题:你在便利店买东西,因为网络延迟一直转圈,你点了很多次支付按钮,怎么避免多次扣款?
《参考解析》
ArrayList 与 LinkedList 的选型:底层结构决定了全部差异——ArrayList 是动态数组,随机访问 get(i) 是 O(1),尾部追加均摊 O(1),但中间插入/删除要搬移元素是 O(n),扩容时按 1.5 倍申请新数组并复制;LinkedList 是双向链表,按下标访问要遍历是 O(n),在已持有节点引用(比如迭代器位置)时插入删除是 O(1),每个元素多两个指针的内存开销,而且因为节点在堆上分散,遍历时缓存局部性差,实际跑起来常数往往比 ArrayList 大好几倍,所以「LinkedList 插入快」只在特定场景成立。实践结论:绝大多数场景默认用 ArrayList,即使是队列也优先考虑 ArrayDeque(除非需要 null 元素或 List 接口);LinkedList 的合适场景是频繁在已知位置头尾增删、或者需要同时当队列/栈用。面试加分点:说清扩容机制、modCount 与 ConcurrentModificationException(fail-fast)、以及需要线程安全时用 CopyOnWriteArrayList(读多写少)而不是 Collections.synchronizedList。
卡号 + 日期查询变慢的排查:先量化再定性,不要上来就猜索引。第一步用慢查询日志和 EXPLAIN(MySQL 8 用 EXPLAIN ANALYZE)拿到真实执行计划:type 是不是 ALL/index、rows 扫描行数、filtered、有没有 Using filesort/Using temporary。第二步判断慢在哪一层:是 SQL 本身(无索引、索引失效、回表太多),还是单表数据量太大(几千万行即使走索引,范围大时回表随机 IO 也会拖死),还是并发/锁等待、磁盘 IO 打满、连接池不够。第三步查索引失效的常见原因:对索引列做函数或运算(WHERE DATE(交易日期) = ...)、隐式类型转换(卡号是 varchar 却传了数字,或反过来)、LIKE '%x' 前缀模糊、联合索引不满足最左前缀、OR 连接非索引列、字符集/排序规则不一致导致 join 无法用索引。第四步看业务口径能不能改:分页深翻(LIMIT 1000000, 20)改成基于游标(WHERE id > last_id)、只查需要的列避免 SELECT *、大范围统计改成预聚合或走离线。改善手段包括建合适的联合索引、覆盖索引避免回表、冷热分离/按日期分区(分区裁剪能让范围查询只扫对应分区)、把明细查询换成汇总表或 ES/OLAP 引擎。
「卡号 + 日期」建了索引,where 交易日期 < 当前日期 效率如何:取决于索引顺序和查询范围。如果是联合索引 (卡号, 交易日期),而查询只给了 交易日期 这一个条件,不满足最左前缀,这个索引基本用不上(MySQL 8 可能走覆盖索引的松散扫描,但通常仍是全索引扫描);如果查询里带上 卡号 = ? AND 交易日期 < ?,那这个索引就是理想的,等值列在前、范围列在后,能在卡号内部做范围扫描,效率很高。如果是单独在 交易日期 上建索引,那么 交易日期 < 当前日期 是个范围查询,选择性与范围大小直接相关:范围覆盖了表里绝大多数行时,优化器会认为走索引再回表不如直接全表扫描,于是干脆放弃索引;即使走索引,因为 交易日期 < 当前日期 通常命中大量行,回表是随机 IO,代价可能比顺序全表扫描更高。此外 交易日期 上再套函数(< 当前日期 若写成 DATE(交易日期) < CURDATE())会直接让索引失效。优化方向是:把条件补全到联合索引最左列、把范围收紧(按卡号或时间分片)、用覆盖索引把需要的列都放进索引里,或者按日期分区让分区裁剪生效。
重复点击支付如何避免多次扣款:分三层防。第一层在前端/网关:按钮点击后立即置灰 + loading 态,但只能降低概率,绝对不能当保证(用户可能换设备、断网重发、脚本刷)。第二层是接口幂等:客户端在进入支付页时先向服务端申请一个全局唯一的支付令牌/订单号(requestId/payToken),每次提交都带同一个令牌,服务端以该令牌做唯一索引或 Redis SETNX 抢占,重复请求直接返回首次的结果而不是再走一次扣款;令牌要设有效期并且一次性(成功后作废)。第三层是数据层与账务层保证:订单状态机只允许 待支付 → 支付中 → 已支付 的单向流转,UPDATE ... WHERE status = '待支付' 依赖行锁和影响行数判断胜出者(谁改到 1 行谁负责调支付渠道);账务流水表对「订单号 + 渠道流水号」建唯一约束,渠道回调也要做幂等(同一个 out_trade_no 重复回调只入账一次);对同一用户短时间内的高频请求再加限流。补充两点:调渠道时要带商户侧唯一单号,让渠道侧也具备幂等能力;如果渠道不支持幂等,就要靠本地流水表对账来发现并冲正重复扣款。这套思路的本质是「幂等键 + 状态机 + 唯一约束 + 对账」,任何一层单独用都不够。