如果需要跨库join,该如何实现?

典型回答 所谓跨库join,指的就是数据分散在不同的数据库中,但是又需要做关联查询,这时候就需要解决跨库join的问题。 举个例子,比如我们有两个数据库一个交易trade库,一个用户customer库,这时候,如果我们想join查询位于trade库的orders表和位于customer库的users表,因为他们是不同的数据库,是没办法直接用以下SQL进行join的: 1 2 3 4 select orders.order_id,users.user_name from orders join users on orders.user_id = users.id 那么,如果要解决这个问题,可以有以下几个方案。 指定库名join 虽然交易trade库和用户customer库是不同的数据库,但是如果他们位于同一个数据库实例中,也是可以进行跨库join的,只需要在JOIN语句中通过指定数据库名来引用它们。例如,在MySQL中: 1 2 3 4 select orders.order_id,users.user_name from trade.orders join customer.users on trade.orders.user_id = customer.users.id 但是这个要求就是他们必须属于同一个数据库实例。 但是,需要注意的是,跨库JOIN操作可能会影响查询性能,尤其是在处理大量数据时。优化查询和考虑索引的使用可以帮助改善性能。 数据冗余 ✅为什么大厂不建议使用多表join? 在上面这篇文章中,我们提及过,有的时候,我们为了避免join,会在数据库表设计的时候,考虑反范式,即做一些字段的冗余。 比如上面的那个例子,我们可以在orders表中把user_name字段冗余下来,这样在一些需要在展示订单的时候展示用户名称的时候就可以不用join了。 但是,这么做也会带来一定的数据一致性的问题,那就是当users表中的用户改了名字,就会导致orders表中的用户名不一致,这时候就需要考虑同时修改,但是如果订单量很大,改起来是成本很高的。 所以,一般来说历史的数据就不改了,这种场景来说,如果用户看自己之前的订单,发现是自己之前的昵称,也是可以接受的。 但是不管怎么说,这个方案毕竟有限制,会带来数据不一致的问题。如果要考虑一致性,就会带来级联修改的问题。 但是,这个方案确实是公司里面用的比较多的。很多时候对于一些不常修改的字段,做一些数据冗余是非常方便的,比如用户的真实姓名。 内存中做join 所谓在内存中做join,其实就是在应用程序中,通过写代码做join。比如: 1 2 3 4 5 6 7 8 9 //先从数据库中查询出要查询的订单列表 List<OrderDO> orders = getOrders(); for(OrderDO orderDO : orders){ OrderDTO orderDTO= new OrderDTO(orderDO); //根据用户ID去users表查询用户名 String userName = getUserNameByUserId(orderDO.getuserId); orderDTO.setUserName(userName); } 这种做法非常多,虽然这么做,代码会比较复杂,然后需要多次查询,但是对于那些没有数据冗余,又没有很复杂的join的场景,还是非常常用的。 ...

March 22, 2026 · 1 min · santu

应用占用内存持续增长,但是堆内存、元空间都没变化,可能是什么原因?

典型回答 如果应用占用的内存在不断地增长,但是堆和元空间等区域都没有明显变化,那么大概率可能和堆外内存的使用有关。所以我们应该考虑有哪些情况会使用到堆外内存。 那么,有哪些场景会使用到堆外内存呢? ByteBuffer未及时回收 Java NIO的ByteBuffer.allocateDirect在使用直接内存时,如果没有正确释放或及时回收,可能导致内存泄露。 ✅什么是堆外内存?如何使用堆外内存? 当我们使用ByteBuffer buffer = ByteBuffer.allocateDirect(1024)分配内存时,会在堆外占用1k的内存,同时会在堆上创建一个ByteBuffer对象,当然这个对象只占用一个对象的指针引用的大小。 当我们在做堆内存分析的时候,如果发现堆上有大量的ByteBuffer对象,虽然他们自身占用的内存空间很小,但是他们关联的堆外内存可能会很大。所以这时候就需要考虑ByteBuffer导致的内存泄露的问题。 堆外缓存使用不当 除了NIO会使用堆外内存,很多缓存框架也会用堆外内存的,如Ehcache、MapDB、OHC等,他们都会使用堆外内存来提升存储空间以及减少对GC的影响。 我们都知道,这些缓存是有一些缓存过期策略的,但是如果没有设置合理,那么就可能导致长期无法被清理,大量占用堆外内存。 日志文件的内存映射 现在, 很多日志框架都支持Memory Mapped File Appender(内存映射)这种日志记录方式,它利用了操作系统的内存映射文件特性来实现日志写入。这种方法通过将文件内容映射到进程的地址空间,允许应用程序像访问内存一样直接访问文件内容,从而提高了写入性能。 在使用内存映射记录日志时,它将日志先写入到映射到内存的文件中。这个过程利用内存映射文件的高效性,减少了实际的磁盘I/O操作。然后再由操作系统定时将内存中的更改同步回磁盘文件。 而这部分使用的内存就是堆外内存。随着日志文件的增长,映射到内存中的大小也会增长。所以会导致我们看到的应用占用的内存的增长。 还有需要注意的是,除了我们应用程序自己的日志,很多框架或者web容器也会有自己的日志缓存的机制。 JNI和本地代码 如果应用程序通过Java本地接口(JNI)调用本地代码或库,那么这些本地操作可能会分配额外的内存,而这部分内存不会在JVM堆内存或元空间中反映出来。所以,也需要检查一下程序中是不是有这种代码。 线程栈 除了堆内内存、堆外内存,还需要考虑一下栈内存,每个线程都有自己的线程栈,随着线程数量的增加,线程栈所占用的内存也会增加。这方面也需要考虑一下。

March 22, 2026 · 1 min · santu

4C8G的机器,各项系统指标,什么范围算是正常?

对于一个配备4核CPU和8GB内存的机器,如果运行Java应用,系统指标的正常范围会依赖于多种因素,以下是一些常见的系统指标及其大致的正常范围: 以下,这个只是我认为的经验值,并不是业内通用标准哈。 CPU 使用率: 在空闲或轻负载情况下,CPU使用率应较低,通常在0-30%左右。 在中等负载下,使用率可能在30-70%。 高负载或高性能需求的情况下,CPU使用率可能会更高,但是最好不要长期超过80%。一般建议控制在70%以下。 系统负载(Load): 对于4核CPU,理想的系统负载值应低于4。超过这个值可能意味着过载。一般来说应该维持在50%以下,也就是2以下。 如果长时间超过75%,也就是3的话,那么就需要进行排查看看是不是存在问题了。 磁盘利用率: 这个指标其实和CPU数和内存都没啥关系,一般来说磁盘超过80%就要报警了。有的日志比较多的应用,可能在60%左右就需要来看了。 一般为了让磁盘的利用率维持在一定水位上,机器都需要配置日志的自动清理。 内存使用率: Java应用会有一定的内存占用,但内存使用率应该保持在合理范围内。通常情况下,70-80%的内存使用率是可以接受的。 如果内存使用率长期接近或达到100%,可能存在内存泄漏问题或内存不足。 JVM堆内存占用: 对于8G的机器内存,一般来说JVM的内存维持在4G-6G之间。 JVM堆内存的占用情况不要超过80%,超过了之后要考虑内存泄漏了。 磁盘I/O: 磁盘I/O取决于应用类型和数据存储方式。正常情况下,磁盘读写应平稳,没有长时间的高峰。 高磁盘I/O等待时间可能表明磁盘性能瓶颈。 GC次数&时长: 一般来说,YoungGC的次数不要超过1次/分钟。YoungGC的时长要控制在50ms以内。 对于FullGC来说,一周内最好不要超过1次,一次FullGC的时长不要超过1秒钟。 ✅FullGC多久一次算正常? 下面是基于上面的信息我整理的表格: CPU利用率(单核) Load 磁盘利用率 内存利用率 堆内存占用率 YGC次数 YGC时长 FGC次数 FGC时长 正常范围 <70% <2 80%以下 <80% <80% 每分钟<1次 <50ms <1次/周 <1s 需要关注范围 70%-90% >3 >80% >=80% >=80% 每分钟>1次 >200ms 1次/天 >2s 不可接受范围 >=100% >4 >=100% >100% >100% 10次/分钟 1s 1次/小时 >=5s

March 22, 2026 · 1 min · santu

和其他公司做数据交互时,有什么需要注意的?

典型回答 在公司间进行数据交互时,无论是通过HTTP接口还是文件传输,我们都需要关注交互过程的安全性、准确性和效率、以及合规性等问题。 安全性 数据交互的安全性至关重要,如何避免双方的交互数据被别人窃取到,如何防止数据泄露都是我们需要关注的。一般来说都需要在接口中进行加解密和加签验签。 协议加密: 在数据传输时,需要确保数据在传输过程中不被窃取或篡改。如果是Restful交互应该优先使用HTTPS协议而不是HTTP协议。对于文件传输,可以考虑使用安全的文件传输协议如SFTP或FTPS,或在传输前对文件进行加密。 数据加密: 不仅要对协议进行加密,敏感数据也需要进行加密,比如手机号、身份证号等,都需要用加密算法进行加密。 发送方在发送数据之前,使用密钥将数据转换成不可读的格式(密文)。接收方使用密钥将密文转换回原始数据(明文) 加密可以用对称加密,也可以用非对称加密。不管是哪种加密,公钥秘钥都是提前约定好的。 1 2 3 4 5 6 //双方约定好的加密key String key = "1234567887654321"; //使用AES进行加密 AES aes = SecureUtil.aes(key.getBytes()); String encryptedData = aes.encryptBase64(originalData); 1 2 3 4 5 //双方约定好的加密key String key = "1234567887654321"; AES aes = SecureUtil.aes(key.getBytes()); String decryptedData = aes.decryptStr(encryptedData); 加签验签: 加签验签的主要目的是保证数据的完整性和确认发送方的身份,确保数据在传输过程中未被篡改,并且是由可信的发送方发送的。 发送方使用密钥对数据(通常是数据的哈希值)进行签名,然后将签名附加到数据上发送。接收方使用相应的密钥验证签名的有效性,以确认数据的完整性和发送方的身份。 通常来说,双方会约定对一些关键字段做加签,并且大多数都是使用带盐的加签算法。 1 2 3 4 // 使用RSA加签 String privateKeyBase64 = "HollisTestKey"; RSA rsa = new RSA(privateKeyBase64, null); String signature = rsa.signBase64(encryptedData, KeyType.PrivateKey); 1 2 3 4 5 // 使用RSA验签 String signature = "";//这里需要从请求中解析出signature String publicKeyBase64 = "HollisTestKey"; RSA rsa = new RSA(null, publicKeyBase64); boolean isCorrect = rsa.verify(encryptedData, KeyType.PublicKey, signature); 身份验证和授权: 对于接口调用,需要进行鉴权,确保调用者有API的访问权限,这时候就需要做授权校验了,这里可以使用 API 密钥或 OAuth 令牌、以及白名单等。 文件完整性校验 如果是文件传输,一般都是直接取sftp上获取文件,这时候为了确保文件的完整性以及未被篡改,都需要做一些校验,比如说对文件做MD5,然后把MD5和文件名单独通过接口发送。接收方在拉取到文件之后自己计算出MD5,再和接口传输过来的MD5做校验即可。 区块链 在有一些金融场景中,也会通过区块链的方式进行数据传输,通常是联盟链。区块链提供了一个分布式的、去中心化的数据库或账本,其中的记录一旦被添加就无法被更改或删除。 传输效率 接口传输的时候,需要保证传输效率,那么我们可以有以下手段来保障: ...

March 22, 2026 · 1 min · santu

4C8G _16台 和 8C16G_8台,不考虑成本的情况怎么选?

典型回答 16台4核8G内存的机器,和8台8核16G内存的机器,总资源看上去其实是相等的,但是他们还是有一些区别的,当我们不考虑成本差异的情况下,对于一个Java应用,可以从以下几个方面来考量如何选择。 单机瓶颈:有一点是可以肯定的,那就是不同的应用对于CPU和内存的需求一定是不一样的,虽然这两个方案的总资源是一样的,但是单机资源是不一样的,也就说在单机上的处理能力是有瓶颈的。比如虽然16台4C8G的机器,但是每一台他毕竟是4C8G,如果要用到的内存超过8G,或者CPU需求超过4C,那么他就会带来性能瓶颈。 容错能力:同时更多的服务器节点也可以提供更高的容错能力。如果一台服务器出现故障,对整体系统的影响会更小。 这个尤其是在应用发布过程中就更加明显,当我们采用金丝雀发布进行滚动发布时,重启1/16和重启1/8机器数,对线上业务的影响肯定是不一样的。 负载均衡:而且,更多的服务器意味着负载均衡的发挥空间更大一些,他可以做更平均地分配负载。 连接数瓶颈:还有,有的时候,资源的瓶颈并不仅仅在机器上,还在于连接数,如应用和数据库的连接数、应用和Redis的连接数等。拿MySQL来说,他给单机分配的连接数是固定的(当然也可以调,但是我司是不让调的,超过了这个数要么就自己扩容, 要么就是存在慢SQL),但是通常意味着,机器数越多,总的可用连接数越多,并发度也就更好。 GC相关:另外,站在内存的角度来看,更大的内存意味着可以给JVM分配的内存也就更多一些,他能放下的对象也就更多,那么他的GC次数可能就会更少。但是同时也意味着GC的时长可能也会更长。对于8G内存,一般我们可以给JVM分配4G,16G内存,一般我们可以给JVM分配8G。虽然4G已经达到了G1的最低标准,但是G1来说8G肯定更优。 扩展能力:当我们需要扩展集群的时候,如果机器本身的内存和CPU更厉害的话,那么可能扩容的机器数就不需要那么多,那么扩展速度可能就会更快一些。 *总体来说,4C8G 16台在并发处理能力、容错能力、负载均衡、连接数瓶颈等方面更加友好,但是需要考虑单机瓶颈及GC次数、以及扩容效率带来的影响。 而且,用更多小型服务器也更加符合当今的微服务架构的特点。想想当年阿里的去IOE,其中的I不就是把IBM大型机、小型机替换成PC Server嘛。 *4C8G 16台 8C16G*8台 单机瓶颈 低 高 容错能力 更好 更差 负载均衡 发挥空间大 发挥空间小 连接数瓶颈 多 少 GC时长 更短 更长 G1使用 也能用 适合 扩展能力 扩展更慢 扩展更快 所以,如果不是特别的"微"服务,那么意味着他可能需要的内存和CPU资源更多,那么可以考虑用配置更高的。 而如果服务拆分的比较细,每个应用的职责比较清晰,简单,那么他对负载均衡,容错能力的要求就更高,考虑用更多的机器。

March 22, 2026 · 1 min · santu

如何预估一个系统的QPS?

典型回答 经常会有这样的面试题,让你预估一个系统可能得QPS有多少。 ✅什么是QPS,什么是RT? 预估是有必要的, 因为有时候我们一个新的业务要上线,或者要搞一个大促,我们就需要合理的做预估,来预留硬件资源。 预估一个系统的QPS通常涉及对业务特性、历史数据、峰值流量和系统架构的综合考虑。以下是一个基本的预估过程。(我先给一个方法论,然后文末再给个示例) 分析业务模型 业务类型:不同的业务类型的QPS是不太一样的,比如B类业务和C类业务,支付业务和购物车业务,这些都是不太一样的。 用户基数:要预估QPS,就要大概知道我们一共有多少用户,人数都不知道,怎么预估出一秒钟有多少人访问呢?所以需要重点考虑目标用户群的规模。 用户行为:分析用户的典型行为模式,如每天的活跃时间、行为频率等。 参考历史数据 如果是已有系统,分析历史流量数据,特别是高峰时段的数据。 对于新系统,可以参考类似系统的公开数据或行业标准。 估算日均请求量 根据用户行为和业务模型估算日均请求总量。 例如,如果预计每个用户平均每天产生10次请求,且有10000个活跃用户,则日均请求量为10 * 10000 = 100000次。 计算峰值流量 峰值流量通常是日均流量的数倍。可以考虑特殊事件(如促销活动)可能带来的流量波动。 例如,如果预计峰值流量是日均流量的3倍,则峰值流量为100000 * 3 = 300000次。 估算QPS 将峰值流量分布到高峰时段。例如,如果高峰时段为2小时,则QPS为 300000 / (2 * 3600) ≈ 42次/秒。 考虑系统的容错和冗余设计,通常需要在估算的基础上增加一定的冗余比例。 预估增长趋势 考虑业务增长和用户增长对QPS的影响。 预估未来一段时间内的QPS增长趋势,以便进行相应的资源规划。 考虑架构和技术因素 分布式架构和负载均衡策略可以显著影响系统的处理能力。 缓存、数据库优化和查询优化也会对QPS有重要影响。 举例 根据上面的方法论,我们预估一下微博的互动QPS情况。 首先,根据公开资料显示,截至2023年,第三季度末,微博月活跃用户达到6.05亿。一般来说,日活用户数是月活用户数的20%-30%左右,按照30%来算的话,那么日活用户数为2亿。 根据微博这个产品的属性,假设每个用户每天产生2次互动,比如发微博、转发评论等。那么日均请求量就是4亿次(2亿*2)。 如果不考虑峰值情况的话,那么把这个4亿次平均分摊一下,那么每秒钟就有4639次请求。(4亿/24/3600) 但是,一般来说,一个业务的峰值流量会是平时的数倍,在不考虑热点事件的情况下,假设峰值流量是日均流量的3倍,并且每天的峰值流量会维持在2小时左右。那么就有以下公式: 假设日常流量的QPS为X: X * (24-2)* 3600 +2 *3 * X * 3600 = 400000000 那么最终可以得出X= 3968。即日常QPS大概为4000左右,峰值QPS为12000左右。 当然,以上预估我们有很多假设,比如假设日活用户数是月活用户数的30%,假设每个用户每天产生2次互动,假设峰值QPS是日常的3倍,假设峰值时长持续2小时。在我们实际业务做预估的时候,也会有些指标是没有具体值的,也会有假设值。这是很正常的,因为如果所有指标都有具体指,那也就不需要预估了。 只不过,假设值越少,预估的结果就越精确。最后,在预估QPS的时候,记得给自己留点buffer。

March 22, 2026 · 1 min · santu

为什么不建议使用MQ实现订单到期关闭?

典型回答 ✅订单到期关闭如何实现 在上面这篇文章中,我们介绍了几种用MQ实现订单的到期关闭的案例。 但是,其实用MQ的延迟消息实现订单的到期关闭并不是一种特别好的方案,尤其是在数据量比较大的情况下。主要存在以下几个问题: 资源占用与成本:如果系统中存在大量订单,为每一个订单都创建一个延迟消息可能会导致消息队列中积压大量的消息,这不仅增加了消息队列的资源消耗,也可能导致增加成本,尤其是在使用云服务提供商的消息队列服务时。 延迟消息的限制(重要):首先并不是所有的消息队列服务都支持延迟消息,即使有一些支持,也可能对消息的延迟时间有限制。例如,某些服务可能限制延迟时间的最大值,这可能无法满足所有订单的到期关闭需求。(尤其是一些B类采购订单,关闭时间可能会比较长) 可靠性问题(重要):虽然消息队列一般来说可靠性较高,但是也没办法做到100%不丢消息,所以在极端情况下,会有丢消息的风险。 精确性问题:消息的投递延迟是比较常见的一种情况,这可能会导致订单关闭操作不够精确。 大量无效消息(重要):使用MQ实现订单到期关闭就要把订单放到MQ中,但是大部分订单会提前取消或者完成支付,这就会导致很多无效的消息。 扩展性问题:随着系统规模的扩大,依赖于消息队列的延迟消息来处理订单到期可能遇到扩展性问题。系统可能需要更复杂的消息队列管理策略和更高效的资源利用策略来应对不断增长的订单量。 这里尤其是可靠性及无效消息的问题比较明显,所以在一个订单量比较大的场景,不是特别建议用MQ实现订单的到期关闭。 一般是通过定时任务来实现,比如阿里内部就搞了个TOC(超时中心),就是基于定时任务来实现的订单到期关闭。

March 22, 2026 · 1 min · santu

为啥不要在事务中做外部调用?

典型回答 很多时候,我们经常会在数据库事务的过程中做一些网络调用,比如Redis的更新、MQ的发送,RPC接口的调用等,其实这是非常不建议的。 1 2 3 4 5 6 7 @Transactional public void order(OrderDTO orderDTO){ //本地数据库操作 orderServive.createOrder(orderDTO); //远程外部调用 mqService.send(orderDTO); } 这么做会带来几个问题: 1、增加事务持续时间:外部的网络调用会显著增加事务的执行时间,特别是网络延迟或外部服务响应慢的情况下。事务持续时间的增加会占用数据库资源更长时间,如锁定资源,这会影响数据库的并发处理能力和整体性能。 本来你操作完数据库就可以提交了,事务和数据库链接也就释放了。但是因为有外部调用,那么就会导致这个事务的时间边长,链接释放时间也变长。 2、死锁和资源竞争:长时间持有事务期间的锁资源会增加死锁的可能性,尤其是当系统负载高时。因为一旦在事务中开启了锁,比如select for update,需要事务提交或回滚后才能释放锁,这就会导致更多的锁冲突。 3、提高事务失败的风险:外部调用增加了事务失败的可能性。如果外部服务调用失败或超时,可能需要回滚事务,这不仅增加了复杂性,还可能导致数据不一致的问题。 这么做非常不好! 比如说在一些场景中,举个例子在用户下单过程,我们要在下单操作成功后发个MQ,但是因为你做了个事务,消息发送失败后,异常被捕获到,就会导致数据库回滚。大家想想,用户下单都成功了,就因为发MQ失败了,就把订单操作给回滚了,这根本就不合理啊!所以,这就会导致很多不在核心链路上的操作失败而导致核心链路回滚。 4、难以保证原子性:事务的一个关键属性是原子性,意味着事务内的操作要么全部成功,要么全部失败。当事务中包含外部调用时,很难保证事务和外部系统之间的原子性,因为外部系统的操作可能不易于回滚。 有这样一种情况,当我们调外部接口的时候,因为网络延迟, 我们拿到了一个timeoutException,这时候我们本地事务回滚了。 但是,远程操作虽然超时了,但是不代表他没执行,所以很可能他已经已经成功了!那么这就导致了数据的不一致! 所以,为了避免这些问题,通常建议的做法是: 将事务和外部调用分离:尽可能在事务提交或完成后,再执行外部调用。这样可以减少事务持续时间,降低失败风险,并保持数据库事务的简洁性和高效性。 使用补偿事务:如果外部调用必须与数据库操作紧密集成,可以设计补偿事务来处理失败情况。补偿事务是一种逻辑上的回滚操作,用于撤销已经执行的外部操作。 异步处理:考虑使用异步消息队列或事件驱动架构来处理需要外部调用的操作。这样可以在不阻塞数据库事务的情况下,异步地执行外部服务调用。

March 22, 2026 · 1 min · santu

如何实现一个抢红包功能?

典型回答 抢红包的功能背后的算法一直没有被公开,但是基于一些网传的资料,以及我和wx内部的朋友的沟通,有一些思路,给大家分享下。这里只讨论拼手气类型的。普通红包要比这个简单的多。 基本原则 拼手气红包的核心是确保每个参与者都有机会获得随机金额的红包,而总金额保持不变。算法需要满足以下基本原则: 公平性:每个人都应该有机会获取不同金额的红包。 随机性:红包的金额应该是随机分配的,不可预测。 非零原则:每个红包的金额最小1分钱。 总额不变:所有红包的总金额等于用户设定的总金额。 二倍均值法 一种常见的实现方法是“二倍均值法”。 二倍均值法的具体算法过程如下: 确定金额范围:对于每次抢红包,确定每个红包金额的范围。假设剩余红包金额为M,剩余红包数量为N,则每个红包金额的上限为(M/N)*2,这确保了后面的人也有机会抢到金额相对较多的红包。 随机分配:根据上述金额范围,使用随机函数生成每个红包的金额,同时确保每个红包的金额不低于最小金额单位。 更新状态:每分配一个红包,更新剩余红包金额和剩余红包数量。 假设有一个总金额为100元的拼手气红包,共10个。对于第一个红包,最大金额为(100/10)*2=20元。如果随机函数生成了一个6元的红包,那么更新后的总金额为94元,剩余红包数量为9个。对于下一个红包,最大金额将为(94/9)*2,以此类推。 其他考虑 抢红包的算法我们基本上了解了,但是抢红包没那么简单,还需要考虑很多其他的问题,比如: 性能问题:抢红包需要在用户请求的那一刻能快速响应,对性能是有一定的要求的。 高并发:考虑到微信红包的高并发性质,后端系统需要优化数据库操作、缓存策略和网络通信,确保在高流量时仍然保持高性能和稳定性。 安全性:防止作弊和攻击,如确保红包分配的随机性不能被预测或操纵。 用户体验:在确保公平性和随机性的同时,还需要考虑用户体验,如避免极端情况下的极小或极大红包金额。 但是,因为一个微信群中的人数是有上限的,并且红包的数量也是有上限的,所以这个并发度其实是可控的,并且对于单个红包来说,热点问题也并没有那么明显。 有人说微信红包金额是提前生成的,这个我问过他们,答案是实时计算的。主要原因是一方面实时计算效率也很高,并且提前算好会占用内存(因为有些红包不会被抢到之后就会退回,所以浪费空间)。 关于高并发防控、热点问题、防止黄牛这些大家可以参考: ✅让你设计一个秒杀系统,你会考虑哪些问题?

March 22, 2026 · 1 min · santu

为什么很多公司数据库不允许物理删除(delete) 数据

典型回答 数据库中的数据删除有两种方式,一种是物理删除,即直接 delete 掉,还有一种是逻辑删除,即通过一个字段标识他已经被删除了。 很多公司都是采用逻辑删除的方式,甚至完全禁止物理删除。为什么呢? 数据留痕(重要) 这一个比较好理解、一旦物理删除了,这数据就都没有了,那么后续想要做数据分析、数据报表、或者历史的问题排查就不方便了。 合规要求(重要) 许多行业,如金融、医疗和法律等,他们受到严格的数据保留法规约束,必须在一定期限内保留所有业务数据以便审计。逻辑删除使得被删除的数据仍然保留在系统中,满足法规审计要求。 数据完整性 物理删除数据可能会影响数据库中的引用完整性。例如,如果一个表的数据被删除,而这些数据正在被其他表引用,则可能导致数据不一致或引用错误。逻辑删除保持数据在数据库中的存在,从而避免了引用完整性问题。 性能考虑 在大规模数据库中,物理删除大量数据可能会对系统性能产生较大影响,因为它可能触发大量的索引重构。逻辑删除通过简单的标记操作减少了这种性能负担。 参考: ✅联合索引是越多越好吗? 导致内存碎片(重要) 在 InnoDB中,delete执行后,只是给数据做了个标记,但空间并不会立即释放。这导致数据页中可能存在大量未使用的空间,增加了数据的分散程度,这就是碎片。 ✅MySQL为什么会有存储碎片?有什么危害? 综上,所以很多公司是直接禁止物理删除数据的,一般都是给表中创建一个 is_deleted 字段,0表示未删除,1表示已删除。 扩展知识 物理删除后的唯一性约束如何设置 ✅数据库逻辑删除后,怎么做唯一性约束? 特殊情况 虽然很多公司不允许线上数据做物理删除,但是也有特殊情况。比如说做数据归档。 有的时候,我们会定期的将历史数据做归档 ,即经他们从当前库腾挪到历史库中,做到冷热数据的分离,这样可以大大降低当前库的数据量,提升查询效率。这时候就可能需要物理删除了。只不过这种删除一般都是基于时间批量删除,并且会在删除后做碎片清理。

March 22, 2026 · 1 min · santu

留言给博主