一个订单,在11_00超时关闭,但在11_00也支付成功了,怎么办?

典型回答 假如,有一笔订单,在10:00下单成功,超时时间是1小时,那么在11点的时候,支付成功了,这时候该如何处理? 明确终态 这是一个比较常见的一个并发处理的问题,而且也是业务中比较常见的问题,一个简单的支付单的状态机如下: 在"支付中"的一笔支付单 ,是有可能推进到支付成功的状态,并且也可能推进到已取消的状态的。 一般来说,正常的支付业务中,支付成功和已取消,都应该是终态,也就是状态机中的最终状态,终态是不能再变化的。 如果一个模型没有明确的终态,或者已经终态的终态数据状态还能随便变化,那么他的设计一定是不合理的。 状态流转控制 那么,如果刚好在同一时刻,同一笔支付单同时来了一个支付成功的消息,和一个超时关闭的请求,该如何处理呢? 首先,我们要做的就是状态机的校验,在我们的支付成功处理和超时关闭的处理过程中,需要做状态的判断,只有支付中的状态才能执行这两个动作。并且数据库的update语句也需要做控制,即: 1 2 3 4 update pay_order set status = "PAY_SUCCESS",lock_version = lock_version + 1 where pay_order_no = #{parOrderNo} and status = "PAYING" and lock_version = #{lock_version} update pay_order set status = "PAY_EXPIRED",lock_version = lock_version + 1 where pay_order_no = #{parOrderNo} and status = "PAYING" and lock_version = #{lock_version} 这时候在发生并发时,就可以确保一个pay_order,要么被推进到PAY_SUCCESS状态,要么被推进到PAY_EXPIRED状态,会且只会发生其中的一种情况。 那有一个成功了,就有一个会失败。这是必然的,这时候就有两种情况了: 1、支付成功处理成功,支付超时处理失败 2、支付超时处理成功,支付成功处理失败 这两种情况如何处理呢? 逆向流程 先说简单的情况,假如支付成功处理成功,支付超时处理失败,这种其实没啥问题,因为已经支付成功了,超时的请求直接拒绝掉就行了。这是业务上正常的逻辑。 ...

March 22, 2026 · 1 min · santu

不使用synchronized和Lock如何设计一个线程安全的单例?

典型回答 如果不能使用synchronized和lock的话,想要实现单例可以通过饿汉模式、枚举、以及静态内部类的方式实现。 饿汉,其实都是通过定义静态的成员变量,以保证instance可以在类初始化的时候被实例化。 静态内部类,这种方式和饿汉方式只有细微差别,只是做法上稍微优雅一点。这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。。。但是,原理和饿汉一样。 枚举,其实,如果把枚举类进行反编译,你会发现他也是使用了static final来修饰每一个枚举项。 其实,上面三种方式,都是依赖静态数据在类初始化的过程中被实例化这一机制的。但是,如果真要较真的话,ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。也正是因为这样, 除非被重写,这个方法默认在整个装载过程中都是同步的(线程安全的)。 那么,除了上面这三种,还有一种无锁的实现方式,那就是CAS。 扩展知识 CAS实现线程安全的单例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Singleton { private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>(); private Singleton() {} public static Singleton getInstance() { for (;;) { Singleton singleton = INSTANCE.get(); if (null != singleton) { return singleton; } singleton = new Singleton(); if (INSTANCE.compareAndSet(null, singleton)) { return singleton; } } } } 用CAS的好处在于不需要使用传统的锁机制来保证线程安全。 ...

March 22, 2026 · 1 min · santu

你是如何进行SQL调优的?

典型回答 SQL调优是面试中经常爱问的问题,这个问题可以考察一个候选人对于SQL的整体性能优化的理解和掌握程度,一般来说,SQL调优需要从以下几个方面和步骤入手。 首先,需要先发现问题,尤其是在面试中,最好是结合业务说明,比如是某一次线下报警出现了慢SQL,或者是接口RT比较长,做了性能分析发现瓶颈是在SQL查询上面都可以。但是不管怎么样,一定要有背景。 有了问题之后,那就是问题的分析了。 首先需要定位到具体的SQL语句,这个可以通过各类监控平台或者工具来实现,通过定位到SQL语句之后,我们就知道具体是哪张表、哪个SQL慢了。 那接下来就是进行分析了,一般一个SQL慢,可能有以下几种原因: 1、索引失效 2、多表join 3、查询字段太多 4、表中数据量太大 5、索引区分度不高 6、数据库连接数不够 7、数据库的表结构不合理 8、数据库IO或者CPU比较高 9、数据库参数不合理 10、事务比较长 11、锁竞争导致的等待 12、深分页问题 所以,一次完整的SQL调优,一般需要考虑以上几个因素,一般会涉及到其中的一个或者多个问题。那么就逐个优化。 首先,索引失效的问题一般是先通过执行计划分析是否走了索引,以及所走的索引是否符合预期,如果因为索引设计的不合理、或者索引失效导致的,那么就可以修改索引,或者修改SQL语句。或者强制执行使用某个索引。具体可以参考: ✅索引失效的问题如何排查? 其次,多表join也是SQL执行的比较慢的一个常见原因,关于这个问题,我们在以下文章中有详细的阐述背景和解决方案: ✅为什么大厂不建议使用多表join? 接下来,如果是索引区分度不高的话,这个其实也和索引不合理有关,但是其实到底快不快,用不用索引,并不是因为区分度高不高导致,其实还是索引扫描的行数的成本导致。所以,有的时候不能认为区分度不高就一定会效率低,或者一定就不适合创建索引。 区分度不高的字段建索引一定没用吗? 查询字段太多,这个有的时候是因为我们错误的用到了select * 导致的,一般来说,查询字段小于100个,都不是特别大的问题,除非真的是字段数特别多,这时候可以采用两种办法解决。第一个就是不要查询那些你不关心的字段,只查询少部分字段。第二个就是做分表,垂直分表,把数据拆分到多张表中。但是这么做可能也会带来需要多表join的问题,所以拆分的时候也需要考虑冗余。 表中数据量太大,一般来说,单表超过1000万,会导致查询效率变低,即使使用索引可能也会比较慢,所以如果表中数据量太大的话,这时候可能通过建索引并不一定能完全解决了。那么具体的解决方案有几种: 1、数据归档,把历史数据移出去,比如只保留最近半年的数据,半年前的数据做归档。 2、分库分表、分区。把数据拆分开,分散到多个地方去,这里不详细介绍了,我们的文档中有分库分表和分区的详细介绍,不展开了。 3、使用第三方的数据库,比如把数据同步到支持大数量查询的分布式数据库中,如oceanbase、tidb,或者搜索引擎中,如ES等。 数据库连接数不够,这个也需要具体分析,到底是什么原因,可能的原因有几个,第一个就是业务量太大了,单库确实扛不住了,那就选择分库吧。 第二个可能就是存在一些慢SQL、或者长事务导致的,慢SQL占用数据库链接,数据库连接数不够,其他的查询就会阻塞,就更慢。 ✅数据库连接池满排查过程 数据库的表结构不合理,这个也是一个关键原因,有的时候比如某个字段中存了很长的内容,或者没有做合理的冗余需要多表关联查询等等。解决思路就是重构,或者分表。 数据库IO或者CPU比较高,这种问题也常见的,当数据库整体IO或者CPU飙高的时候,查询速度就有可能下降,所以需要分析背后的原因及解决思路,可以参考: ✅数据库CPU被打满排查过程 存在长事务,这个和慢SQL同理,都是占用了数据库链接,导致其他请求要等待。 **锁竞争导致的等待,**当有大并发争抢共享资源的时候,就会导致锁等待,这个过程就会拉长耗时,导致SQL变慢。这个也可以参考上面的CPU被打满的问题。 **数据库参数不合理,**这个也是经常会遇到的,针对我们具体的业务场景,做一些适当的参数调整,有时候也能大大的提升SQL的效率。比如调整内存大小、缓存大小、线程池大小等。 深度分页问题是指在数据库查询中,当你尝试访问通过分页查询返回的结果集的后面部分(即深层页码)时遇到的性能问题。可以考虑使用子查询以及记录上一页ID的方案解决。 ✅MySQL的深度分页如何优化 扩展知识 参数优化 假设我们有一个名为 mydb 的数据库,其中包含一个名为 mytable 的 InnoDB 表。该表有一个自增主键 id,一个整数类型字段 age 和一个字符串类型字段 name,我们希望对该表进行优化。 首先,我们可以使用 SHOW VARIABLES LIKE ‘innodb%’; 命令查看当前的 InnoDB 参数设置。这些参数包括缓冲池大小、刷新间隔、日志大小等等。 接下来,我们可以尝试调整以下几个参数来优化数据库性能: innodb_buffer_pool_size: 缓冲池大小是 InnoDB 存储引擎的核心参数之一,它控制着 InnoDB 存储引擎使用的内存大小。通常,我们可以将该参数设置为系统可用内存的 70%-80%。例如,如果系统有 8GB 内存可用,我们可以将 innodb_buffer_pool_size 设置为 6GB。在 MySQL 中,可以使用以下命令进行设置: ...

March 22, 2026 · 1 min · santu

索引失效的问题是如何排查的,有哪些种情况?

典型回答 MySQL的索引失效是一个比较常见的问题,这种情况一般会在慢SQL发生时需要考虑,考虑是否存在索引失效的问题。 在排查索引失效的时候,第一步一定是找到要分析的SQL语句,然后通过explain查看他的执行计划。主要关注type、key和extra这几个字段。 ✅SQL执行计划分析的时候,要关注哪些信息? 我们需要通过key+type+extra来判断一条SQL语句是否用到了索引。如果有用到索引,那么是走了覆盖索引呢?还是索引下推呢?还是扫描了整颗索引树呢?或者是用到了索引跳跃扫描等等。 一般来说,比较理想的走索引的话,应该是以下几种情况: 首先,key一定要有值,不能是NULL 其次,type应该是ref、eq_ref、range、const等这几个 还有,extra的话,如果是NULL,或者using index,using index condition都是可以的 如果通过执行计划之后,发现一条SQL没有走索引,比如 type = ALL, key = NULL , extra = Using where 那么就要进一步分析没有走索引的原因了。我们需要知道的是,到底要不要走索引,走哪个索引,是MySQL的优化器决定的,他会根据预估的成本来做一个决定。 那么,有以下这么几种情况可能会导致没走索引: 1、没有正确创建索引:当查询语句中的where条件中的字段,没有创建索引,或者不符合最左前缀匹配的话,就是没有正确的创建索引。 2、索引区分度不高:如果索引的区分度不够高,那么可能会不走索引,因为这种情况下走索引的效率并不高。 3、表太小:当表中的数据很小,优化器认为扫全表的成本也不高的时候,也可能不走索引 4、查询语句中,索引字段因为用到了函数、类型不一致等导致了索引失效 这时候我们就需要从头开始逐一分析: 1、如果没有正确创建索引,那么就根据SQL语句,创建合适的索引。如果没有遵守最左前缀那么就调整一下索引或者修改SQL语句 2、索引区分度不高的话,那么就考虑换一个索引字段。 3、表太小这种情况确实也没啥优化的必要了,用不用索引可能影响不大的 4、排查具体的失效原因,然后针对性的调整SQL语句就行了。 扩展知识 可能导致索引失效的情况 假设我们有一张表(以下SQL实验基于Mysql 5.7): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 CREATE TABLE `mytable` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `age` int(11) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`), KEY `age` (`age`), KEY `create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; insert into mytable(id,name,age,create_time) values (1,"hollis",20,now()); insert into mytable(id,name,age,create_time) values (2,"hollis1",21,now()); insert into mytable(id,name,age,create_time) values (3,"hollis2",22,now()); insert into mytable(id,name,age,create_time) values (4,"hollis3",20,now()); insert into mytable(id,name,age,create_time) values (5,"hollis4",14,now()); insert into mytable(id,name,age,create_time) values (6,"hollis5",43,now()); insert into mytable(id,name,age,create_time) values (7,"hollis6",32,now()); insert into mytable(id,name,age,create_time) values (8,"hollis7",12,now()); insert into mytable(id,name,age,create_time) values (9,"hollis8",1,now()); insert into mytable(id,name,age,create_time) values (10,"hollis9",43,now()); 索引列参与计算 1 2 3 4 5 6 7 select * from mytable where age = 12; +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+ | 1 | SIMPLE | mytable | NULL | ref | age | age | 5 | const | 1 | 100.00 | NULL | +----+-------------+---------+------------+------+---------------+------+---------+-------+------+----------+-------+ 以上SQL是可以走索引的,但是如果我们在字段中增加计算的话,就会索引失效: ...

March 22, 2026 · 8 min · santu

InnoDB为什么不用跳表,Redis为什么不用B+树?

典型回答 Innodb是MySQL的执行引擎,MySQL是一种关系型数据库,而Redis是一种非关系型数据库。这两者之间比较大的区别是:关系型数据库以表的形式进行存储数据,而非关系型数据库以Key-value的形式存储数据。 MySQL是基于磁盘存储的,Redis是基于内存存储的。而B+树是一种磁盘IO友好型的数据结构,跳表则是一种内存友好型的数据结构。 为什么说B+树是磁盘IO友好的呢? 首先,B+树的叶子节点形成有序链表,可以方便地进行范围查询操作。对于磁盘存储来说,顺序读取的效率要高于随机读取,因为它可以充分利用磁盘预读和缓存机制,减少磁盘 I/O 的次数。 其次,由于B+树的节点大小是固定的,因此可以很好地利用磁盘预读特性,一次性读取多个节点到内存中,这样可以减少IO操作次数,提高查询效率。 还有就是,B+树的叶子节点都存储数据,而非数据和指针混合,所以叶子节点的大小是固定的,而且节点的大小一般都会设置为一页的大小,这就使得节点分裂和合并时,IO操作很少,只需读取和写入一页。 ✅InnoDB为什么使用B+树实现索引? 所以,B+树在设计上考虑了磁盘存储的特点和性能优化,我们曾经分析过,当Innodb中存储2000万数据的时候,只需要3次磁盘就够了。 ✅从B+树的角度分析为什么单表2000万要考虑分表?? 而跳表就不一样了,跳表的索引节点通过跳跃指针连接,形成多级索引结构。这导致了跳表的索引节点在磁盘上存储时会出现数据分散的情况,即索引节点之间的物理距离可能较远。对于磁盘存储来说,随机访问分散的数据会增加磁头的寻道时间,导致磁盘 I/O 的性能下降。 那么,为啥Redis要用跳表实现ZSET呢(不只是跳表,详见下面链接)?而不是B+树呢? ✅Redis中的Zset是怎么实现的? 因为Redis是一种基于内存的数据结构,他不需要考虑磁盘IO的性能问题,所以,他完全可以选择一个简单的数据结构,并且性能也能接受的 ,那么跳表就很合适, 因为跳表相对于B+树来说,更简单。相比之下,B+树作为一种复杂的索引结构,需要考虑节点分裂和合并等复杂操作,增加了实现和维护的复杂度。 而且,Redis的有序集合经常需要进行插入、删除和更新操作。跳表在动态性能方面具有良好的表现,特别是在插入和删除操作上。相比之下,B+树的插入和删除需要考虑平衡性,所以还是成本挺高的。

March 22, 2026 · 1 min · santu

从B+树的角度分析为什么单表2000万要考虑分表??

典型回答 理论上来说,只要磁盘空间够,存多少都可以,但是随着数据量的增多,查询效率会下降的,根据实际经验来说,单表抗2000万数据量,通过索引查询问题不大,那么这个数字确实是一个经验值,但是他背后是不是有一定的计算逻辑呢?如何计算出这个数据的呢? 首先我们需要知道的事,单表能存数据不用考虑分库分表,这要看记录大小、存储引擎设置、硬件配置等多种因素。那么如果一定要做数据计算,我们可以从B+树存储的角度来分析一波。 B+树的高度限制 B+树是InnoDB存储引擎使用的索引结构,我们都知道,随着表中数据量的增加,B+树的高度会逐渐增加。如果B+树的高度过高,每次查询需要经过较多的层级,会导致查询性能下降。因此,B+树的高度限制是单表存储量的一个瓶颈。对于B+树的高度限制,一般建议将B+树的高度控制在3到4层以内,以获得更快的查询性能。 数据页 [✅介绍一下InnoDB的数据页,和B+树的关系是什么?](https://www.yuque.com/hollis666/ec96i7/vebvlntlc6rnvuu0) 我们都知道,Innodb中的数据页默认大小是16KB,并且B+树的每个节点都对应着一个数据页,包括根节点、非叶子节点和叶子节点。B+树的非叶子节点对应着数据页,其中存储着主键+指向子节点(即其他数据页)的指针。B+树的叶子节点包含实际的数据行,每个数据行存储在一个数据页中。 估算 有了以上的背景之后,我们就可以基于B+树的高度及结构,还有数据页的大小,来估算单表的数据量了。 我们都知道,B+树的叶子节点和非叶子节点存储的内容是不一样的。所以需要区分开来进行计算。 我们很容易知道以下公式: 能存多少条记录=叶子节点的数量*每个叶子节点中能存的数量。 叶子节点的数量=根节点之下的第一层非叶子节点的数量^(树高度-1) 最终我们只需要计算出非叶子节点的数量、叶子节点中能存的数量、树高度就行了。 非叶子节点的数量 一个根节点中,可以扩展出多少个子节点? 我们已知一个根节点的存储量是16KB,并且他作为非叶子节点,他只需要存储一个主键+一个指针就行了。假设是一个bigint类型的主键(8字节),和默认6字节的指针。那么可以存储: 16 * 1024 / (8+6) ≈ 1170 根节点可以扩展出1170个二层高度的子节点,而三层的B+树则会有两层非叶子节点。那么最终就能关联出 1170 * 1170 = 1,368,900个叶子节点。 叶子节点的存储行数 已知一个叶子节点有16KB,那么它能存储多少数据量就取决于单行数据的大小了。假设单行数据量1KB,那么他就能存储16条,假设单条数据量500 B 那么他就能存储32条。 估算结果 基于以上计算方式,假设单条数据的存储空间是1KB,那么3层高度的B+树最终的可存储数据量为: 1170 * 1170 * 16 = 21,902,400,即2000万!

March 22, 2026 · 1 min · santu

MySQL千万级大表如何做数据清理?

典型回答 当我们要清理表中的历史数据时,一般都是通过时间来进行判断的,执行delete的语句如下: 1 DELETE FROM table_hollis WHERE `gmt_create` < SUBDATE(CURDATE(),INTERVAL 300 DAY); 如上SQL,就是删除300天之前的数据,如果是小表的话,执行这个SQL没啥问题,但是如果是大表,如果表中的数据量达到千万级别的话,就会有问题了。 像以上这样的SQL,如果没有在gmt_create字段上创建索引,那么delete操作就会进行全表扫描,进行大范围的加锁,甚至效果相当于锁表,而锁表给业务带来的影响就是业务都无法进行写操作了,这肯定是无法接受的。 而且,即使业务说我可以允许锁表,上面的操作也有可能会失败,因为数据库会对单条SQL产生的bin_log的大小是有限制的,删除这么大量的数据,产生的日志大小如果超过该阈值,最终还是会失败! max_binlog_cache_size参数指定了单个事务最大允许使用的Binlog,当超出这个值时,会出现报错:Multi-statement transaction required more than ‘max_binlog_cache_size’ bytes of storage; increase this mysqld variable and try again 而且,删除操作还涉及到磁盘IO,如果要删除的数据太多,就会导致频繁的IO,对数据也会造成一定的压力。 还有就是,数据的删除过程,也会伴随着索引更新,大量的数据删除操作,会因为频繁的索引重建而导致业务无法进行写操作。 那么,怎么解决呢?如何实现高效、安全的大表的批量删除呢? 这里可以参考阿里云DMS的数据清理功能的方案(我司内部的数据库用的就是你这个方案——https://help.aliyun.com/document_detail/162507.html ) 他的做法总结一句话就是:DMS在清理数据时会扫描全表,根据主键或非空唯一键分批执行。 1、获取要做数据清理的表的主键,或者非空唯一键的最大值和最小值。 如: 1 select min(id) as min_id,max(id) as max_id from table_hollis; 假如我们得到min_id = 100,max_id = 100000; 2、分段取出第一个区间的所有数据,默认区间可能是1000,也可以根据binlog配置等进行调整。 1 2 3 4 5 6 7 8 9 10 11 select id,( //查出符合条件的数据 select 1 from ( //查出第一个区间的所有gmt_create select gmt_create from table_hollis where id >= 100 and id <= 100000 order by id asc limit 1000 ) t where gmt_create < SUBDATE(CURDATE(),INTERVAL 300 DAY) limit 1 ) as hasNeedDelItem from table_hollis where id >= 100 and id <= 100000 order by id asc limit 1000; 这段 SQL 代码的主要目的是查询出表 table_hollis 中 id 值在 100 到 100000 之间的记录,并为每条记录增加一个额外的字段 hasNeedDelItem。这个字段用于标识是否存在一个条件满足的记录。以下是详细的逐步解析: ...

March 22, 2026 · 2 min · santu

为什么MySQL用B+树,MongoDB用B树?

典型回答 Innodb是MySQL的存储引擎,MySQL是一种关系型数据库,而MongoDB是非关系型数据库。关系型和非关系型数据库比较大的区别就是关系型数据库有大量的范围查询,而非关系型数据库基本都是单条查询。 还有一个区别就是,InnoDB引擎下的MySQL的数据是存储在磁盘上的,而MongoDB的数据是存储在内存上的。 MongoDB 是基于磁盘做存储的,它将数据持久化在磁盘上。 但是,为了提高性能,MongoDB 结合了内存映射的方式来加速数据操作。当有查询或操作时,MongoDB 会将频繁访问的数据加载到内存中,这样可以避免频繁访问磁盘,从而提升读取速度。 接着说一下B树和B+树的区别,其实他们的主要区别就在于B+树的叶子节点之间通过双向指针链接进而形成了链表,所以B+树在范围查询时更加高效。 ✅什么是B+树,和B树有什么区别? 一般来说,**B+树在磁盘存储和范围查询方面具有较高的效率,**因为B+树在范围查询时可以通过顺序遍历叶子节点来获取连续的数据,从而提高了查询的效率。**而B树更适合内存存储和随机访问的场景,**因为B树的特点是叶子节点和非叶子节点都包含键值和数据信息。这种设计使得B树在内存中的访问更加高效,因为在内存中可以直接访问数据,而不需要再进行一次磁盘访问。 所以说,如果是磁盘查询,一定是B+树效率更高,这是一定的。所以这是MySQL用B+树的原因! 但是如果是内存查询,B树一定就比B+树好吗?虽然他可能会因为非叶子节点上也有数据而减少查询次数,但是B+树相比B树来说,他还有一个同样的数据量情况下,他的高度可能更低的优点呢。。。 所以,在内存查询上,并不能说明B树就一定比B+数的效率要高。 那么,为啥MongoDB还要用B树呢?其实,MongoDB在旧版本中用的确实是B树,但是在 MongoDB 3.2 以后,已经采用 WiredTiger 作为默认的存储引擎了。 WiredTiger的默认的数据结构已经是B+树了 (https://source.wiredtiger.com/10.0.0/tune_page_size_and_comp.html )!

March 22, 2026 · 1 min · santu

怎么做数据对账?

典型回答 在分布式系统中,虽然我们会使用各种分布式事务的方案,来保证各个系统之间的一致性。但是,很多时候往往事与愿违。 尤其是现在很多公司都采用最终一致性的方案,而所谓最终一致性,无论是本地消息表、事务消息、还是任务重试,系统之间的调用都是有可能失败的。 而一旦发生失败,就需要有一套机制来发现这些不一致的问题,这时候就需要做数据对账了。 一般来说,根据对账的时机分为两种,离线对账和(准)实时对账。 实时对账,一般来说基本都是准实时,也就是说并不能保证无延迟,但是一般可以控制在秒级别的延迟上。而离线对账一般都是D+1的核对。 D+1:D指的是自然日,包括工作日和节假日。+1指的是第二天,也就是说数据发生后的第二天进行核对。 T+1 :T 指的是交易日,一般来说就是工作日,所以T+1指的就是数据发生后的下一个工作日进行核对。 那么对账的技术实现上一般主要就是两种,要么是写代码核对,要么是写SQL核对。 写代码就是查出需要比对的两条记录,然后进行字段的比对,不一致的就抛出来。 写SQL就是做join查询或者子查询,然后通过where条件比较需要核对的字段,不一致的就抛出来。 写代码这种核对方式,一般来说都是通过定时任务实现的,通过运行定时任务,然后去扫表,或者去远程拉数据,然后在业务代码中进行核对,这种方式的优点就是比较通用,不管是数据库,还是文件,还是远程接口,都可以做核对。缺点就是一旦数据量大了,代码核对的时效性就会比较差,而且代码运行存在失败的可能。万一数据量特别大,就可能会出现扫表扫不动,文件加载到内存导致OOM等问题。 所以,写代码的核对方式不推荐。 那么,更好一点的方式就是写SQL了,因为数据都在数据库(数仓或者大数据框架)中,这些都可以通过SQL进行查询,如下就是比较两个系统中金额是否一致的SQL: 1 2 3 SELECT out_biz_no,bill_no,owner FROM bill_item where out_biz_no in ( select biz_id from collection_case_item_detail where case_item_state = "COLLECTING" and cur_ovd_principal > 0 ) and charge_on_amount - charge_off_amount = 0 那在哪写这些SQL进行核对,就有很多种方案了,一般来说有以下几个: 1、离线数仓,主要用于离线数据核对。我们现在每天会把需要离线存储的数据同步到数仓中,然后在数仓中写SQL进行数据的核对。 2、在线数据库,离线核对的话发现问题比较慢,好一点的方案是在在线库做核对,可以直接在数据库中写SQL,进行数据核对。为了避免数据核对影响真实业务,可以考虑在备库中执行SQL。但是有的时候数据核对可能是多个系统之间的,这时候就要做跨库join,但是并不是所有的数据库、所有的引擎都支持跨库join的。 3、准实时数据库,还有一种方案,那就是不直接在数据库中写SQL,而是把数据同步到其他的地方,比如通过监听binlog的方式,把MYSQL的数据同步到实时数仓,比如我们公司内部用的就是AnalyticDB,把需要做核对的数据同步到ADB中,我们会尽量放到一个空间下面,然后在这里面写SQL作核对。同步出来的这个ADB数据,不仅可以做核对,还可以用于查询或者做报表。 4、ETL核对,还有一种比较常见的方案,那就是通过ETL工具进行数据核对,ETL包括了数据的提取、清洗、转化及加工,所以在这个过程中也是可以做核对的。 5、flink核对,flink是一个非常牛X的流处理框架,可以通过flink进行数据核对。 扩展知识 最佳实践 我在阿里待了8年,见证了我司的准实时核对系统的几代更迭。离线的核对基本就是离线数仓核对了,没啥变化。 最开始监听binlog,然后把binlog转成事件,有一个核对系统专门监听这个事件,然后我们自己在上面写Java代码实现数据核对。 后来直连数据库的备库,直接通过写SQL语句的方式做核对。 到现在基于flink监听数据库变更,然后把数据写入adb中,核对平台直连ADB,写SQL语句做核对。 所以,我见过的这几个方案,应该也是目前业内比较流行的几种方案了。 核对与告警 有朋友提问:如果通过sql语句发现有不一致的数据,一般是怎么处理?是发告警通知开发人员还是说需要将有问题的数据标记出来过段时间重新核对? 我们一般来说,告警的话,人工一定要跟进。 至于说过段时间重新核对,这种我们一般在报警规则中把他屏蔽掉了。有需要重新核对的,我们会做一些延迟核对,或者二次核对。或者直接报警规则设置成多次失败后再报警,或者告警阈值调高一点。 这样就是职责单一,核对就是核对,报警就是报警。有报警人就要看,否则都是无效报警,那有效报警也没人看了。 日切点数据误报问题 ✅数据对账时,如果日切时间点前后的数据不一致怎么办?

March 22, 2026 · 1 min · santu

MySQL热点数据更新会带来哪些问题?

典型回答 热点数据更新是指频繁更新某个或某些特定行的数据,比如大促期间,有一个大商家,如小米旗舰店、Apple旗舰店等他们的某个商品,会因为有很多人要买,所以他就是热点商品,而大家在短时间内同时买这个商品的时候,就会热点更新这个商品的库存,这就是所谓的热点数据更新,或者叫做热点行更新,如: 1 2 3 UPDATE products SET stock_quantity = stock_quantity - 10 WHERE product_id = 10086; 热点数据的更新是我们需要避免的,因为他存在以下问题: 1、锁竞争,热点数据的更新是通过update语句进行的,而update是需要给记录增加排他锁的,这就会导致大量的请求被阻塞。降低整个系统的吞吐量。 2、占用数据库连接,当有大量的update语句,因为要修改同一条记录而被阻塞的时候,他们持有的数据库连接是不会释放的,而数据库连接又是有限的,所以会导致连接数不够,进而影响整个系统的吞吐量及可用性。 3、耗尽数据库CPU,大量锁等待,就会导致大量的自旋,多个线程就会不断的尝试获取锁,CPU就需要不断的执行自旋操作,并且需要做死锁检测,消耗大量CPU时间。并且在这个过程中,操作系统也需要频繁的进行线程上下文的切换,这个过程会导致CPU时间片的浪费。 ✅数据库CPU被打满排查过程 4、死锁风险,在高并发的情况下。由于数据库需要频繁定位和更新这些特定行,可能会增加锁竞争和死锁的风险,影响并发性能。 5、索引维护开销大,频繁的更新热点数据,不仅会导致数据的变化,还可能导致相关索引的频繁维护,这可能会增加数据库的开销,导致性能下降。 6、主从不一致,热点数据的频繁更新,如果在主从复制出现延迟的情况下,就会放大数据不一致的概率。 扩展知识 如何解决和避免 ✅MySQL怎么做热点数据高效更新? ✅高并发的积分系统,在数据库增加积分,怎么实现?

March 22, 2026 · 1 min · santu

留言给博主