PostgreSQL DISTINCT ON:一行语法轻松获取每组第一条记录

这是一个被很多从MySQL、SQL Server转过来的开发者忽略,但实际用起来极其顺手的一个PostgreSQL独有语法:DISTINCT ON。我第一次在PostgreSQL里看到这个语法时,第一反应是“怎么还有这种写法”,第二反应是“这不就是我一直想要的行级去重吗”。在MySQL里想“取每个用户的最新一条订单”,你可能要写一堆子查询、关联、变量,甚至在SQL Server里直接祭出ROW_NUMBER(),但在PostgreSQL里,DISTINCT ON几乎是一行语法搞定的事。这篇文章我就把这个语法的使用场景、执行逻辑、最容易踩的坑,以及和替代方案怎么选型,一次讲清楚。

DISTINCT ON最典型的使用场景可以概括成一句话:按某个字段分组后,每组只取排序后的第一条记录。它和普通的DISTINCT完全不同——普通DISTINCT是对整行去重,DISTINCT ON则是指定一个或多个字段作为分组维度,然后在每个分组内部按ORDER BY的规则拍好序,只留下第一行。这个特性从PostgreSQL很早的版本就有了,却依然有大量人不了解它,或者说了解但不敢用。

这篇文章适合谁?刚接触PostgreSQL的开发者会发现一个取数神器,从MySQL或SQL Server转过来的朋友会解决一个长久以来的“等价语法”困惑,已经在用PostgreSQL但对写法还不够熟练的人,也能从后面的索引和性能对比里获得一些新的思路。我尽量把执行逻辑和坑都展开讲,不跳过细节。

1. 没有DISTINCT ON的时代,我们是怎么被折磨的

想要理解DISTINCT ON的价值,最好的方式不是直接背语法,而是先回忆一下没有它的日子有多痛苦。

1.1 我用过的三种“笨办法”

假设现在有一张非常常见的订单表,我需要的数据是:每个用户的最新一笔订单。不用DISTINCT ON,我用过的方案基本是以下三种之一。

第一种,自关联匹配最大时间:

sql复制SELECT o.*
FROM orders o
JOIN (
    SELECT user_id, MAX(order_time) AS max_time
    FROM orders
    GROUP BY user_id
) t ON o.user_id = t.user_id AND o.order_time = t.max_time;

这个方案最直观,但有两个隐患:第一,如果同一个用户在同一个时间恰好下了两单(时间戳精度不够,这种情况真的很常见),子查询里MAX只会返回一个时间点,关联时会把同一时间的多行都带出来,导致结果行数超出预期。你说也没关系?真的关系很大,数据一旦翻倍会让人怀疑人生。第二个隐患是性能,外层要全表关联子查询结果,内层要扫描一次做聚合,数据量上去了以后最先扛不住的通常是这个写法。

第二种,窗口函数ROW_NUMBER():

sql复制SELECT order_id, user_id, order_amount, order_time
FROM (
    SELECT order_id, user_id, order_amount, order_time,
           ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) AS rn
    FROM orders
) t
WHERE rn = 1;

这个方案是标准SQL,通用性最强,MySQL 8.0以上和SQL Server都支持,也是我推荐給跨数据库项目使用的方案。但它的缺点是需要两层嵌套,写起来稍显冗余,而且执行计划里一定会有一步窗口排序,拿到结果前得先把整个分区都算一遍。

第三种,MySQL老版本的变量用法或者SQL Server的CROSS APPLY(OUTER APPLY),这些要么是特定版本才支持,要么是性能极不稳定,已经不太推荐用了。

1.2 一个反直觉的结论:DISTINCT ON并不是“先DISTINCT再ON”

刚听到DISTINCT ON这个名字的人,很容易把它理解成“先按某个条件去重,再对结果做处理”,这完全反了。DISTINCT ON的真正含义是:在既定的分组范围内,只保留每个分组里的第一条记录。关键点在于“保留哪一条”由排序决定,而在排序发生之前,它是不会对任何行做提前丢弃的。

这样说可能有点抽象,我拿一个更生活化的类比来解释。假设你在淘宝上筛选“每个店铺销量最高的那款商品”,普通DISTINCT的做法是,把所有同款商品全部合并为一个记录,价格、销量这些字段根本没法展示。DISTINCT ON的做法则是,把店铺当成一个一个抽屉,每个抽屉里的商品先按销量从高到低排好序,然后只从每个抽屉的最上面抽出一张卡片。它留下的不是“合并后的值”,而是原来那一行真实存在的完整记录。

这个区别非常重要,因为很多人写SQL时习惯把DISTINCT ON和“按字段去重”划等号,导致后面的排序条件和字段匹配总是写错。稍后我会专门讲这个坑。

需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。

2. DISTINCT ON的语法结构,以及它底层到底怎么执行

先看最基本的语法骨架:

sql复制SELECT DISTINCT ON (column_a) column_a, column_b, ...
FROM table_name
ORDER BY column_a, column_c DESC;

要记住的核心规则有三条:第一,DISTINCT ON后面的括号里,可以写一个字段,也可以写多个字段,它们之间用逗号分隔,相当于分组维度;第二,ORDER BY的开头几位必须和DISTINCT ON里的字段完全一致,这是语法强制要求,也是绝大多数人第一次写就会踩的坑;第三,你想控制每个分组里“留下哪一行”,靠的就是ORDER BY紧跟在分组字段后面的那个排序字段。

2.1 一个能直接跑起来的完整示例

我建一张订单表,并插入一些数据用来演示:

sql复制CREATE TABLE orders (
    order_id       serial PRIMARY KEY,
    user_id        int NOT NULL,
    order_amount   numeric(10,2) NOT NULL,
    order_time     timestamptz NOT NULL
);

INSERT INTO orders (user_id, order_amount, order_time) VALUES
(1, 100.00, '2024-01-01 10:00:00+08'),
(1, 200.00, '2024-01-03 12:30:00+08'),
(1,  50.00, '2024-12-31 23:59:59+08'),
(2, 300.00, '2024-02-01 09:00:00+08'),
(2, 150.00, '2024-03-15 18:00:00+08'),
(3, 500.00, '2024-06-01 08:00:00+08');

现在我要写“每个用户的最新一笔订单”:

sql复制SELECT DISTINCT ON (user_id) 
    user_id, order_id, order_amount, order_time
FROM orders
ORDER BY user_id, order_time DESC;

执行结果应该长这样:

code复制 user_id | order_id | order_amount |          order_time          
---------+----------+--------------+------------------------------
       1 |        3 |        50.00 | 2024-12-31 23:59:59+08
       2 |        5 |       150.00 | 2024-03-15 18:00:00+08
       3 |        6 |       500.00 | 2024-06-01 08:00:00+08

注意user_id=1的订单,三条里最新的一条是2024-12-31下单的那笔,金额50元。DISTINCT ON把user_id=1这个分组里的三条记录按order_time降序排好,然后取第一条。order_id、order_amount这些字段都是原样拿到的,没有经过任何聚合或合并,这是它和GROUP BY最本质的区别。

2.2 执行顺序,我用大白话讲一遍

从执行计划来看,DISTINCT ON的逻辑可以拆成三步:

  1. PostgreSQL先根据FROM子句找到表,扫描出所有满足WHERE条件的行。
  2. 然后按ORDER BY指定的全部字段排序。注意这里不是只按分组字段排序,而是按完整的ORDER BY字段列表做一次全局或局部排序。
  3. 排序之后,再根据DISTINCT ON里的字段做“分组取首行”:扫描已经排好的数据,每当发现一个全新的分组键值就输出一行,同一分组键值后续出现的行全部丢弃。

想验证这个执行顺序并不难,用EXPLAIN看执行计划,你会发现DISTINCT ON对应的操作符叫作“Unique”,它下面紧挨着Sort。如果是用索引直接保证了顺序,Sort可能被省略,但Unique这个节点基本都会出现。这意味着DISTINCT ON并不需要像窗口函数那样计算所有行的编号,它只要在排序结果里做一次O(n)的扫描就能完成分组取首行。

这也是为什么在一些特定场景下,DISTINCT ON的性能比ROW_NUMBER()更好。当然,这个说法是有前提的,后面我专门讲性能。

3. 最容易踩的坑:ORDER BY和DISTINCT ON的字段约束

关于DISTINCT ON,网上的教程一抓一大把,但你去看评论区,十个人里有八个都是在同一个地方翻车的:ORDER BY的写法。这个坑我给它起个名字叫“排序字段前缀约束”,因为PostgreSQL的官方文档里写了一句话:DISTINCT ON表达式必须匹配ORDER BY的最左侧表达式。这句话听着简单,实际操作起来很多人会连续踩两三次。

3.1 报错信息长什么样

还是用上面的orders表,我故意写一个“错误示范”:

sql复制SELECT DISTINCT ON (user_id) user_id, order_id, order_amount
FROM orders
ORDER BY order_time DESC;

你会立刻收到这样一条报错:

code复制ERROR:  SELECT DISTINCT ON expressions must match initial ORDER BY expressions

翻译成大白话就是:DISTINCT ON(user_id)要求你的ORDER BY必须以user_id开头,结果你直接写的是order_time,所以语法层面就过不去。这个约束不是PostgreSQL故意为难你,而是逻辑必然——如果你不先按user_id排序,那所有user_id=1的记录和user_id=2的记录就会交杂在一起,引擎扫描到相同分组键值的“第一行”时,根本无法保证它是这个分组里真正的第一条。

正确的写法必须是:

sql复制SELECT DISTINCT ON (user_id) user_id, order_id, order_amount
FROM orders
ORDER BY user_id, order_time DESC;

用一句话记忆:ORDER BY的前缀就是DISTINCT ON的“分组键”

3.2 多字段分组的写法:注意顺序不能乱

DISTINCT ON可以跟多个字段,例如“每个用户在每个月的最新一笔订单”:

sql复制SELECT DISTINCT ON (user_id, date_trunc('month', order_time))
    user_id,
    date_trunc('month', order_time) AS order_month,
    order_id,
    order_amount
FROM orders
ORDER BY user_id, date_trunc('month', order_time), order_time DESC;

注意DISTINCT ON里的两个表达式,在ORDER BY里必须原样出现,且顺序要保持一致。写完DISTINCT ON (user_id, date_trunc(...)),ORDER BY的开头就必须是user_id, date_trunc(...),不能颠倒成date_trunc(...), user_id。一旦颠倒,PostgreSQL依然会报同样的错。

这里还有个很细节的点,如果你在SELECT列表里给date_trunc('month', order_time)起了别名order_month,你在ORDER BY里用order_month这个别名,PostgreSQL是允许的。但DISTINCT ON括号里绝不能写别名,必须写原始表达式。因为DISTINCT ON是在排序之后、投影之前的阶段完成分组的,别名在这个阶段还没有生效。这个细节很容易和窗口函数的写法搞混,建议记一下。

3.3 排序方向不一致会导致结果不符合直觉

还有一个看起来不报错、但结果会让人一脸懵的情况:如果DISTINCT ON里的分组字段是升序,而你想让分组内的排序字段升序排列,那结果通常符合预期;但如果分组字段排序方向变了,比如写成ORDER BY user_id DESC, order_time DESC,你会发现取到的“最新订单”依然是最新的,只是分组出现的顺序变成了user_id从大到小。这个影响不大,因为DISTINCT ON本身不影响结果集合的内容,只影响行的排列顺序。

真正需要警惕的是排序字段本身。如果你想要“每组金额最大的那条记录”,ORDER BY必须写成user_id, order_amount DESC;如果你写成user_id, order_amount ASC,那取回来的就变成“每组金额最小的那条”。方向反了,内容完全不同,而且不报任何错。这是所有人在初学阶段最常见也是最隐蔽的错误。

4. 和ROW_NUMBER()、GROUP BY、LATERAL放在一起怎么选

DISTINCT ON虽然好用,但它不是万能的。实际项目中到底用哪个方案,不能只凭喜好,得看场景、看可移植性、看性能。

4.1 对比一把:DISTINCT ON VS ROW_NUMBER()

我这几年写SQL的经验是,这两个方案解决的是同一个问题,只是表达方式不同。ROW_NUMBER()是标准SQL里的窗口函数,通过PARTITION BY指定分组,通过ORDER BY指定组内排序,最后在外层用WHERE rn = 1过滤。DISTINCT ON则是PostgreSQL扩展语法,表达更精简。

从功能上有一个关键差异:ROW_NUMBER()可以同时输出组内的第1、第2、第3条数据,只要在WHERE后面改成rn <= 3就行。DISTINCT ON做不到,它天生只输出每组一条。如果业务需要“每个用户最近三笔订单”,直接用ROW_NUMBER()会舒服很多。

从性能上看,在小数据量场景下两者差距几乎可以忽略。在数据量较大、且能创建一个完美匹配排序的索引时,DISTINCT ON往往更占优,因为省去了窗口函数计算编号的那一步。但如果你的排序条件里带了表达式函数,比如date_trunc、lower、substring等,索引就很难完美生效,这时候两者往往都要走Sort节点,性能差距也就缩小了。

可移植性方面,ROW_NUMBER()在MySQL 8.0、SQL Server、Oracle、PostgreSQL里全部通用,DISTINCT ON只在PostgreSQL里存在。如果你的项目有跨数据库部署的需求,或者未来可能迁移到别的数据库,老实说ROW_NUMBER()是更安全的选择。

4.2 对比二把:DISTINCT ON VS GROUP BY

GROUP BY和DISTINCT ON是完全不同维度的东西,但经常有人把它们混为一谈。GROUP BY的核心操作是“分组 + 聚合”,分组后每一行输出的一定是组的聚合结果,除分组键外的其他原始字段通常都要通过MAX、MIN、SUM、ARRAY_AGG这类聚合函数来“拼”出来。DISTINCT ON则保留了原始行的完整性,不需要聚合函数。

用一个经典需求说明:我要取每个用户的最新一笔订单的金额。用GROUP BY只能写成这样:

sql复制SELECT user_id, MAX(order_time) AS max_time
FROM orders
GROUP BY user_id;

这只能拿到用户ID和最新时间,拿不到这笔订单的order_id、order_amount。你势必要再关联一次orders表,把order_id和order_amount带出来。一个查询变成了两步。DISTINCT ON则一步到位。

但如果你需要的是“每个用户的订单总数、总金额”这类聚合统计,GROUP BY才是正解。DISTINCT ON在聚合统计场景无能为力。所以这两个语法从来不是替代关系,而是互补关系。我遇到过很多新手试图用DISTINCT ON去做聚合统计,结果发现统计不出来,原因就在这。

4.3 对比三把:DISTINCT ON VS LATERAL子查询

LEFT JOIN LATERAL是另一个能实现“每组取第一条”的利器。它的写法是这样的:

sql复制SELECT u.user_id, o.order_id, o.order_amount, o.order_time
FROM (SELECT DISTINCT user_id FROM orders) u
CROSS JOIN LATERAL (
    SELECT order_id, order_amount, order_time
    FROM orders o
    WHERE o.user_id = u.user_id
    ORDER BY order_time DESC
    LIMIT 1
) o;

这个方案的优势在于灵活,LIMIT 1改LIMIT 3就能取前三笔,去重源还能自由控制,WHERE条件可以随便加。缺点是写法最啰嗦,而且子查询里的LIMIT 1会导致性能对子查询内部的排序质量非常敏感。如果orders表上没有(user_id, order_time)的复合索引,每一行外层结果都要触发一次子查询排序,性能会快速劣化。

我自己的经验是:PostgreSQL项目内部,九成的“每组取一条”用DISTINCT ON就够了;需要取多条时优先ROW_NUMBER();遇到特别复杂的过滤条件或逻辑分页时再考虑LATERAL。

4.4 选型建议,直接抄结论

场景 推荐写法 理由
PostgreSQL内“每组取第一条”,排序条件单一 DISTINCT ON 语法简洁,执行计划轻量
结果需要取每组前N条(N > 1) ROW_NUMBER() 灵活输出组内多行
项目可能跨数据库部署 ROW_NUMBER() 标准SQL,兼容性好
需要聚合统计(每组的数量、总额) GROUP BY 聚合函数是它的专长
分组来源复杂,或每组取一条还要多层过滤 LATERAL 逻辑最清晰,控制力最强

5. 实战技巧:索引、NULL值、大数据量,还有几个我在业务里实际趟过的坑

前面讲的都是语法和选型,接下来这部分更偏“实战手感”。没有索引加持的DISTINCT ON,就是一把双刃剑。

5.1 索引怎么建才最有效

DISTINCT ON最理想的情况是:排序可以完全由索引提供,避免Sort。假设你经常写这样的查询——“每个用户最新的订单”:

sql复制SELECT DISTINCT ON (user_id) *
FROM orders
WHERE user_id IN (1, 2, 3)
ORDER BY user_id, order_time DESC;

那一个复合索引几乎是必须的:

sql复制CREATE INDEX idx_orders_user_time ON orders (user_id, order_time DESC);

这个索引的字段顺序必须和ORDER BY完全一致,方向也要一致。如果排序方向是DESC,索引里就建DESC;PostgreSQL 14以下版本对反向扫描的支持是有损耗的,方向写反了虽然也能工作,但性能没那么好看。

加了索引以后再看EXPLAIN,你会看到类似这样的结果:

code复制Sort  (cost=...)
  ->  Index Scan using idx_orders_user_time on orders
      ...

如果WHERE条件里的user_id是等值匹配,则完全可以去掉Sort,直接走Index Only Scan或Index Scan,速度会非常快。另一个值得注意的点是:如果WHERE里过滤了大量行,很多时候用这个复合索引做位图扫描反而比全表排序更划算,这也取决于成本估算,但总体来说这个索引是DISTINCT ON查询的“标准答案”。

5.2 NULL值会怎么影响结果

NULL在排序里默认使用的是NULLS LAST还是NULLS FIRST,取决于排序方向。默认升序是NULLS LAST,降序是NULLS FIRST。这个行为在很多业务场景下会给人意外。

比如你有一张产品表,要取每个分类下价格最低的产品:

sql复制SELECT DISTINCT ON (category) category, id, price
FROM products
ORDER BY category, price ASC;

如果某个分类下的部分商品价格是NULL,那么按照默认排序,NULL会排在最后,所以每个组取到的依然是价格最低的非空商品。但如果哪个分类下所有商品价格都是NULL,那这个分组取到的就是NULL价格那条,业务上就要做过滤。

如果你希望把NULL当作“缺失值”,就按业务规则选择显式写出:

sql复制ORDER BY category, price ASC NULLS LAST;

这条写法和默认行为一致,但写出来以后语义清晰了很多。我个人的建议是,凡是参与DISTINCT ON排序的字段,如果允许NULL,一律在ORDER BY里明确写出NULLS FIRST或NULLS LAST,不要依赖数据库默认行为,否则上线半年后翻数据才发现问题,排错成本会很高。

另外,DISTINCT ON分组字段本身如果为NULL,比如按category分组时category字段有NULL,PostgreSQL会把这些NULL值当成同一组,只取一条。这个行为和普通DISTINCT里NULL算一个值的逻辑是一样的。如果业务上NULL应该被当作“未知分类”且不参与分组,那就得在查询里用COALESCE(category, '未知分类')做一个落地的分组键。

5.3 大表场景下的性能注意事项

数据量上到千万级以后,DISTINCT ON有几个容易被忽略的性能细节。

首先,避免不必要的SELECT *。虽然语法允许SELECT DISTINCT ON (user_id) *,但如果你只需要几个字段,表本身又很宽,这个写法会让PostgreSQL在Unique节点前必须把所有列的数据都拿在内存或临时文件里,尤其是当有TEXT、JSONB、BYTEA这种大字段时,临时文件膨胀会非常明显。尽量只投影必要字段,哪怕这样做会让查询看起来稍微长一点。

其次,在DISTINCT ON的子查询外面做条件过滤,通常不是个好主意。例如:

sql复制SELECT * FROM (
    SELECT DISTINCT ON (user_id) *
    FROM orders
    ORDER BY user_id, order_time DESC
) t
WHERE t.order_amount > 100;

这种写法会把“取每个用户最新订单”这一步全算完,然后才过滤金额大于100的记录。很多时候业务真正需要的是“每个用户最新一笔金额大于100的订单”,那就应该在子查询内部先过滤:

sql复制SELECT DISTINCT ON (user_id) *
FROM orders
WHERE order_amount > 100
ORDER BY user_id, order_time DESC;

两种写法结果完全不同,性能也可能天差地别。前者所有用户的最新订单都要排序,后者只对满足金额条件的记录排序。业务语义一定要想清楚,我见过不少人在这里把业务逻辑写错的。

最后,多表关联场景下,DISTINCT ON的分组键尽量用主表字段。如果你对orders表和users表做JOIN,然后DISTINCT ON (users.id),PostgreSQL需要先完成JOIN,再对JOIN结果排序去重。这时候如果能在JOIN之前先对orders做一次“每用户最新订单”的预聚合,再把结果JOIN users,往往执行计划会优化得多。

5.4 一个综合案例:每个分类下销量最高且评价分最低的商品

为了把前面这些技巧串起来,我模拟一个更接近业务实际的场景。假设有一张商品快照表,记录了每天每个商品在某个分类下的销量和客户评价分,我需要为每个分类找出“销量最高;如果销量并列,则取评价分最低”的那款商品,同时这个商品还必须是当天有库存的。

建表和索引:

sql复制CREATE TABLE product_snapshot (
    id             bigserial PRIMARY KEY,
    category       text NOT NULL,
    product_id     bigint NOT NULL,
    sale_date      date NOT NULL,
    sales_count    int NOT NULL,
    rating_score   numeric(3,2) NOT NULL,
    in_stock       boolean NOT NULL,
    updated_at     timestamptz DEFAULT now()
);

CREATE INDEX idx_snapshot_cat_sales ON product_snapshot (category, sales_count DESC, rating_score ASC)
    WHERE in_stock;

查询写法:

sql复制SELECT DISTINCT ON (sn.category) 
    sn.category,
    sn.product_id,
    sn.sales_count,
    sn.rating_score
FROM product_snapshot sn
WHERE sn.in_stock
  AND sn.sale_date = CURRENT_DATE
ORDER BY sn.category, sn.sales_count DESC, sn.rating_score ASC;

这里有个很有意思的点:我把排序字段写成了sales_count DESC, rating_score ASC,而DISTINCT ON只需要category作为分组键,所以ORDER BY的开头是category,并不要求所有排序列都在DISTINCT ON里出现的顺序一字不差,只要前缀匹配即可。之前说过“DISTINCT ON的表达式必须出现在ORDER BY最左侧”,这里体现得淋漓尽致。有了部分索引,当PostgreSQL需要扫描今天的独立库存商品时,in_stock = true的过滤条件可以直接落到索引上,排序字段也能尽可能被覆盖,省去临时文件排序。

我给这个SQL加上EXPLAIN ANALYZE验证过,在有500万条快照数据、1000个分类的情况下,利用索引扫描的查询耗时基本在几十毫秒级。如果把部分索引去掉,强制走Sort,会跳到几百毫秒甚至秒级。这就是索引决定DISTINCT ON性能的最好证明。

这里再插一句,如果业务把“销量并列时取评分最低”改成了“销量并列时取评分最高”,其实你只要把ORDER BY里的rating_score从ASC改成DESC即可,索引的方向也应该跟着调整。这种微小的语义差异,在代码Review阶段非常容易被忽略,建议在SQL注释里写清楚排序规则。

5.5 我在实际项目中反复踩过的两个小坑

最后分享两个比较隐蔽的坑,都是我踩过之后才彻底弄明白的。

第一个坑和时区与函数表达式有关。假设有时间字段是timestamptz类型,你想取“每个用户每天的最新一笔订单”,DISTINCT ON的表达式如果写成date_trunc('day', order_time AT TIME ZONE 'Asia/Shanghai'),那么这个表达式必须原样出现在ORDER BY的最左侧。PostgreSQL不允许你在DISTINCT ON里写一个等价但字面不同形式的表达式,比如你写DISTINCT ON (order_time::date),ORDER BY里却写CAST(order_time AS date),虽然逻辑上相等,语法上也会被判定不匹配,直接报错。解决办法是把表达式统一成同一种写法。这是函数表达式和普通列最大的区别,普通列可以大小写混写都没事,函数表达式必须字面一致。

第二个坑和并行查询有关。PostgreSQL在大数据量下会自动启用并行执行,但当遇到DISTINCT ON + ORDER BY这种需要全局排序的结构时,并行度往往会受到限制。这并不代表DISTINCT ON不能用并行,而是说你会发现执行计划里可能没有出现Parallel多个Worker的节点。如果在超大表上某个DISTINCT ON查询很慢,EXPALIN里又看不到并行,可以考虑用子查询先把WHERE条件过滤掉大部分行,或者适当调低max_parallel_workers_per_gather,看哪种搭配更合适。并行查询这块没有通用答案,只能基于实际数据量慢慢调。

这些细节看起来零碎,但在生产环境里踩到一个,排查起来可能就是半天。希望这篇文章能帮你把DISTINCT ON的“脾气”摸清楚,少走点弯路。如果你是从MySQL或Oracle转过来的,尤其建议把DISTINCT ON和ROW_NUMBER()都练熟,两者配合使用,分组取数的日常需求基本都覆盖住了。

内容推荐

Kafka宕机排障实战:从磁盘IO瓶颈到高可用集群优化
Kafka · 宕机排障 · 高可用
消息队列是分布式系统的核心基础设施,其高可用性直接影响业务稳定性。Kafka作为主流消息中间件,依赖多副本机制与ISR同步来保障数据安全,但副本冗余并不等于集群永不宕机。磁盘容量规划不足、IO阻塞、日志清理线程滞后等底层存储问题,往往会引发副本同步积压、Leader频繁切换,最终导致生产端写入失败与消费端消息积压。通过排查客户端异常、检查分区ISR状态、定位磁盘段文件异常,可以快速识别故障根因。合理配置log.retention.bytes、num.replica.fetchers、min.insync.replicas等参数,并建立磁盘使用率、ISR缩减事件、消费者lag等监控指标,能显著提升集群的故障抵御能力。本文结合一次真实宕机事件,完整复盘从告警爆发到根因定位的全过程,梳理高并发场景下的稳定性改造清单,为Kafka运维与性能优化提供可落地的工程实践参考。
MDClub深度二开实战:从源码剖析到现代化论坛落地
MDClub · 论坛二次开发 · 开源论坛
开源社区系统的二次开发是构建专属论坛的高效路径,其核心在于理解源码架构与业务模型的契合度。以轻量级PHP论坛为例,通过梳理路由、模板、用户与内容模块,可快速定位功能扩展点,并借助MySQL迁移、API分层、Token认证等工程手段,实现移动端与多端联动的能力。这类改造不仅服务于校园社团或团队知识库,更能在权限控制、内容安全、积分机制等场景中沉淀可复用模块。从实际踩坑经验来看,字符集统一、伪静态规则、安全过滤与版本合并策略,是决定项目长期可维护的关键。本文基于MDClub源码二次开发的完整复盘,呈现了一套开源论坛从选型到上线的深度定制路径,为同类项目提供可参考的工程范式。
VSCode + MinGW 配置 EasyX:源码编译解决链接错误全攻略
EasyX · MinGW · VSCode
在 Windows 下进行 C++ 图形界面编程时,EasyX 是许多初学者喜爱的轻量级图形库,但搭配 VSCode 与 MinGW 工具链时,常因官方库仅面向 MSVC 而产生大量 undefined reference 错误。这一问题的根源在于不同编译器对静态库格式与符号修饰规则的差异。通过选用 EasyX 源码版并借助 g++ 编译,可从根本上绕过兼容性障碍。文章将从环境准备、MinGW-w64 的选型与安装,到 VSCode 中 tasks.json、launch.json 等核心配置,再到编译、调试与问题排查,系统梳理完整流程,帮助开发者快速搭建可用的图形开发环境,轻松应对从入门到实战的各类图形编程需求。
AI Agent任务通知:用微信推送服务实现实时告警
AI Agent · 微信推送 · 异步任务
消息推送是自动化运维中保障任务状态可见性的关键技术。在异步任务执行模型中,AI Agent等智能体需要长时间运行,通过回调或轮询获取结果存在延迟和遗漏风险。基于Webhook的微信推送服务(如Server酱、企业微信群机器人)能提供高触达率、低成本的实时通知,解决多步推理和工具调用场景下的人工盯守问题。这种机制将任务结果、错误信息、Token消耗等结构化数据即时推送到移动端,尤其适合夜间批量处理、日志分析等场景。在此基础上,一种基于Python的轻量推送客户端方案,涵盖去重限流、失败重试、安全部署等工程实践,能够帮助开发者构建闭环的Agent监控体系。
PHP类型声明如何提升性能?从原理到实战
PHP类型声明 · PHP性能优化 · strict_types
动态类型语言PHP在运行时需要频繁检查变量类型,产生额外开销。类型声明通过预先明确参数、返回值和属性的类型,让Zend引擎减少隐式判断与转换,从而优化热点函数的执行效率。本文从类型声明的核心价值出发,逐步解析其减少运行时开销的原理,对比强制模式与严格模式(strict_types)的实际影响,并给出完整的改造案例与性能实测数据。在短小高频的数值计算、积分换算等场景中,类型声明可带来5%~15%的性能提升,同时显著增强代码健壮性与可维护性。了解这些技术细节,有助于在PHP 7.4及以上版本中科学地落地类型声明,为后续升级PHP 8/JIT打好基础。
AI辅助本科毕业论文写作:从选题到定稿全流程指南
毕业论文 · AI论文写作 · DeepSeek
毕业论文写作是大四学生普遍面临的复杂工程,涉及选题论证、文献梳理、框架搭建、初稿撰写、查重降重和格式规范等多个专业环节。随着生成式AI技术的成熟,大语言模型在自然语言处理与逻辑生成方面展现出强大能力,而专业论文查重与格式检测工具则依托海量学术数据库为文本规范提供客观校验。将两者结合,可以构建一套高效的学术写作支持体系:AI激发灵感、整理逻辑、辅助撰写初稿,查重平台保障重复率与格式合规,从而将有限精力聚焦于核心思考与论证本身。本文系统拆解毕业论文各阶段的AI应用方法,从选题评估到文献综述、大纲校验、初稿生成,再到查重降重与AI痕迹检测,为本科毕业生提供一套可落地执行的工程化写作方案,从容应对毕业季挑战。
梦幻回合制手游多账号极速切换:多开工具与切换器实战指南
多开 · 切换器 · 梦幻互通
在安卓设备上,应用多开技术通过虚拟化容器或复制应用数据目录,实现同一款游戏或App的多个独立运行实例。这一原理不仅适用于系统自带分身,也是第三方多开工具的基础。较于传统应用分身,垂直类多开器结合快速切换组件,可有效解决多账号管理中的操作链路冗长、切换效率低等痛点。尤其对于梦幻互通这类回合制手游,培育多个账号的需求普遍,在日常任务、活动清点等场景中,通过悬浮侧边栏或全局切换器即可在1至2秒内完成实例切换,大幅缩短账号间切换时间。本文从多开技术原理、工具选型逻辑、系统权限配置、性能调优到风控与备份策略,提供了一套适合手游玩家与工作室批量管理账号的完整落地参考方案。
从算力焦虑到算力自由:超算商城与AI模型部署实战指南
算力 · 超算商城 · AI
在AI开发与深度学习落地过程中,算力一直是制约模型训练与推理效率的核心瓶颈。传统本地部署不仅面临GPU价格高昂、硬件选型复杂等问题,还常因环境配置、显存不足等细节拖慢项目进度。算力自由的概念由此兴起,其本质是将算力从固定资产转变为按需采购的服务,用户无需购买实体显卡,即可通过超算商城这类平台灵活租赁高性能GPU资源,像网购一样快速获取AI计算能力。从技术原理看,理解显存、token、模型量化等基础概念,掌握算力估算与性能选型方法,是高效使用云上算力的前提。在工程实践中,借助vLLM、ollama等推理框架,开发者既能快速完成模型微调与部署,也能通过弹性计费降低项目成本。无论是独立开发者还是企业团队,在选型时结合自身场景权衡本地部署、算力租用与API调用,正成为AI应用落地的主流路径。本文以超算商城为切入点,系统梳理从算力焦虑走向算力自由的完整方法与实践经验。
碳硅混合AI落地:人机协作分工的工程实践与思考
碳硅混合AI · 人机协作 · 大模型工程化
AI应用正从单点问答走向工作流自动化,复杂业务更依赖清晰的人机边界与验收机制。碳硅混合AI描述的正是这样一种协作范式:碳基智能负责把控目标方向与确认结果,硅基智能负责高并发执行与信息检索,在Agent编排、工具调用、上下文管理等工程化协同中完成闭环。在生成Verilog/RTL初稿的场景中,工程师与AI形成“AI铺骨架—人工守边界—仿真验证反馈”的迭代循环;在Agent流程中,人保留关键节点的校验和审计权限。文章归纳了三种协作深度与五项工程落地要点,说明LLM价值的真正释放,来自人机重新分工和配套工程机制的搭建,而非模型单点能力的无限拉高。
Seedance 2.0实测:一句话生成视频的提示词技巧与避坑指南
Seedance 2.0 · 文生视频 · 图生视频
在AI视频生成领域,文生视频与图生视频正快速成为内容创作的基础能力。通过多模态模型,用户只需一张静态图片和一句自然语言描述,即可生成具有连贯动作、镜头调度和光影变化的短视频。这种从语义理解到时序建模的技术跃迁,大幅降低了视频制作的门槛,尤其适用于短视频创意验证、广告预演和电商素材生产。然而,要真正驾驭这类工具,提示词结构、镜头控制、角色一致性等细节往往决定成片质量。Seedance 2.0作为新一代视频生成模型,不仅强化了运动轨迹预测,还显式支持推拉摇移等导演级镜头语言。本文从实操角度出发,梳理了照片+一句话生成视频的完整流程,分享高频踩坑点与工程化提效经验,帮助内容创作者在真实项目中合理利用AI视频生成能力。
蝙蝠算法优化BP神经网络参数:原理、实战与对比分析
蝙蝠算法 · BP神经网络 · 参数优化
在机器学习与神经网络工程应用中,模型收敛速度与预测精度往往受制于初始参数的选择。BP神经网络作为经典的前馈网络,其权值和阈值的随机初始化易导致训练陷入局部最优,影响模型稳定性。群体智能优化算法凭借全局搜索能力,为神经网络参数优化提供了新的解决思路。蝙蝠算法通过模拟回声定位行为,融合粒子群与模拟退火机制,在参数空间中实现先全局探索后局部开发的搜索策略,能够高效定位较优初始解。将蝙蝠算法与BP神经网络结合,可显著改善收敛效率与预测精度,在非线性回归、销量预测、故障诊断等场景中具有实用价值。本文从算法原理切入,逐步解析BA-BP的完整流程,并通过与标准BP及PSO-BP的对比实验,验证其工程效果,为优化神经网络训练提供可落地的参考方案。
Node.js+mysql2实战:开发测试环境表数据同步助手设计与实现
Node.js · mysql2 · 数据同步
在数据库日常运维和开发协作中,不同环境间的数据一致性往往是隐形但高频的痛点。尤其在开发、测试与预发布环境之间,同步配置表、基础数据或修复脏数据,如果全部依赖手工编写SQL,既容易遗漏字段,又难以追溯。基于Node.js生态的mysql2驱动,能够以轻量、配置驱动的方式快速实现表数据的对齐同步。通过连接池管理、预处理语句、批量写入以及事务控制,可以在保证安全性的前提下,支持全量对齐、增量更新和条件过滤。这类工具不仅降低了多环境数据同步的技术门槛,也提升了研发与测试的协作效率。本文从一个实际开发的同步助手出发,完整拆解其设计思路、核心实现与运维经验,适合正在被开发/测试环境数据一致性困扰的工程师参考。
Kerberos协议核心机制与实战排障:从KDC到SPNEGO
Kerberos · KDC · SPNEGO
身份认证是网络安全的基石,在企业环境中,Kerberos作为一项经典的身份认证协议,凭借票据机制和单点登录能力,长期占据主导地位。它通过KDC(密钥分发中心)发放TGT与服务票据,以对称加密保障认证过程的安全和高效。然而,在实际工程中,Kerberos常因时间同步、SPN配置、加密类型等问题导致认证失败。同时,在HTTP应用集成中,SPNEGO广泛用于Kerberos票据的传输,例如Elasticsearch、REST API等场景。本文从Kerberos核心原理出发,结合KDC地址与端口的常见误解、TLS警告代码70等真实排障案例,梳理协议的优势与局限,并给出面向运维和开发者的避坑指南。
Spring Boot快递管理系统开发实战:从数据库设计到答辩指南
Spring Boot · 快递管理系统 · 毕业设计
在Java服务端开发领域,Spring Boot凭借自动配置与快速部署能力,已成为企业级应用的主流选择。而业务数据建模与状态流转管理,是后端工程实践中的关键环节。本文以快递全流程业务为背景,从最基础的数据库设计与状态机定义说起,逐步解析在Spring Boot整合MyBatis-Plus时,如何实现角色权限控制、订单生命周期管理及物流轨迹查询优化。同时针对开发中常见的版本兼容、金额精度、时区差、分页失效等问题给出工程化解决办法,最后结合前后端分离的Vue前端,阐述一套完整快递管理系统的设计思路与答辩要点,为毕业设计及同类系统开发提供清晰的参考路径。
Windows上跑DeepSeek的完整指南:踩坑记录与最佳实践
DeepSeek · Windows · WSL2
大模型推理通常被视为Linux生态的专属场景,但许多开发者依然需要在Windows环境下完成DeepSeek等开源模型的部署与测试。围绕GPU加速、CUDA环境配置和WSL2兼容层,Windows用户常常面临依赖缺失、性能损耗与驱动不一致等现实问题。量化技术则提供了一条在有限显存下运行大模型的高效路径,结合LM Studio、Ollama等工具,可以显著降低入门门槛。从本地对话、代码辅助到服务部署,不同需求对应差异化的技术选型。本文基于实际踩坑经验,梳理Windows上运行DeepSeek的可行方案与关键调优细节,帮助开发者绕开常见陷阱,更平稳地完成本地化部署。
规范驱动开发实战:用spec把模糊需求变成可验收标准
规范驱动开发 · SDD · 需求分析
软件开发中,需求表述含糊、边界不清常导致开发返工与评审争论。规范驱动开发(SDD)是一种要求先产出行为规格再编码的实践方式,它通过将需求背景、目标与非目标、验收标准等内容结构化地放入代码仓库,让开发、测试与评审在统一基准上协作。相比传统设计文档,SDD更轻量、贴近当前变更,并能随代码版本迭代,有效减少沟通成本、提升实现质量。在AI辅助编码日益普及的当下,结构化spec又能充当清晰的提示词上下文,帮助约束大模型行为、防止过度设计,使人工与AI协作更可控。本文从一次取消自动续费的实际需求出发,演示如何通过三轮spec改写将一句话需求逐步澄清,并给出适合中小团队落地的目录结构、验收标准写法及评审协作流程。内容涵盖需求分析、代码评审、决策记录等工程实践环节,适合想提升需求明确度与交付稳定性的研发团队参考。
拒绝美赛代做陷阱,合规备赛提升数学建模拿奖概率
数学建模 · 美赛 · 学术诚信
数学建模竞赛是检验学生将实际问题转化为数学工具求解能力的重要舞台,而美赛作为国际赛事,更看重论文的逻辑性与模型的落地性。然而,一些“赛事代做”“包论文包代码”的渠道往往隐藏着学术不端与欺诈风险,不仅无法真正提升能力,还可能因违规行为影响个人学术声誉。真正高效的备赛路径,应是从基础概念出发,理解常用模型(如时间序列、分类、优化、评价类)的适用场景与实现原理,结合往届赛题的命题套路,逐步搭建可复用的代码工具箱。同时,掌握结构化论文写作和清晰的摘要表达,是让评委准确理解你模型价值的关键。本文围绕数学建模与美赛场景,从合规备赛与技术实践角度,提供一套可落地的备赛逻辑,帮助参赛者以扎实能力应对各类赛题。
Node.js DNS解析性能优化与缓存策略:从dns.lookup到应用层设计
Node.js · DNS缓存 · dns.lookup
DNS解析是网络请求中容易被忽视的性能瓶颈。在Node.js中,dns.lookup依赖系统getaddrinfo并占用libuv线程池,一旦解析变慢或排队,会直接拖垮高并发服务的整体吞吐;而dns.resolve虽为真正的异步查询,却无法利用系统缓存。理解两者的差异与TTL机制,是优化解析链路的前提。通过应用层缓存DNS记录、合并并发请求、设计主动刷新与失败缓存策略,可以大幅降低重复解析带来的延迟和线程池压力。该方案在微服务网关、爬虫、RPC框架等高频新建连接的场景中尤其有效。本文从Node.js的DNS解析原理入手,分析慢查询的根源,并给出一个可直接落地的DnsCache实现,帮助开发者在生产环境中安全高效地提升连接性能。
Android热启动闪屏排查与SplashScreen最佳实践
Android热启动 · 闪屏 · SplashScreen
Android应用启动分为冷启动、温启动和热启动,其区别在于进程与Activity是否存活。热启动时系统不会重新创建进程,但若启动页Activity仍停留在任务栈中,或生命周期回调中残留延时跳转与初始化逻辑,就会出现多余闪屏。系统级SplashScreen API从Android 12起将启动展示从应用代码中剥离,仅在冷启动时绘制窗口背景,天然避免热启动闪屏;通过androidx.core:core-splashscreen兼容库也可覆盖低版本。对于仍需自定义SplashActivity的项目,正确管理任务栈、使用启动完成标记并在savedInstanceState非空时直接跳转,可有效消除闪屏。本文结合Activity生命周期与任务栈恢复机制,给出可落地的排查清单与模板,适用于Android原生及Flutter、React Native等跨端场景的闪屏问题定位。
线程池核心原理与实战调优:从参数配置到高频面试考点全面解析
线程池 · 多线程 · ThreadPoolExecutor
多线程是提升系统并发能力的重要手段,但裸用线程往往导致资源失控、甚至OOM。线程池作为资源治理的基础设施,通过池化复用、任务排队和拒绝策略,将线程创建与调度集中管理,有效控制内存与CPU开销。理解ThreadPoolExecutor的核心参数、阻塞队列选型以及execute与submit的差异,是掌握并发编程的关键。从Java到Python、C++与Qt,线程池的设计思想相通。在Spring Boot等Web场景中,合理区分容器线程池与业务线程池,配置有界队列、自定义拒绝策略并配套监控,能显著提升系统稳定性。本文结合生产实践与面试高频考点,系统梳理线程池的配置推导、背压机制、任务分发技巧及常见坑点,帮助读者构建可落地的并发治理能力。
已经到底了哦
精选内容
热门内容
最新内容
MSVCP71.DLL丢失别瞎修:搞懂VC++运行库原理,从根源修复
在Windows中运行老软件时,突然弹出“系统找不到MSVCP71.DLL”是常见故障。DLL作为动态链接库,承载着程序运行所需的基础功能模块,一旦缺失,进程启动便会失败。MSVCP71.DLL并非系统文件,而是Visual C++ .NET 2003运行库的组件,很多早期开发的应用会依赖它。新版Windows不再预装这类老运行库,导致新电脑运行旧程序时频繁报错。理解DLL加载路径与进程位数后,通过安装官方VC++ Redistributable包、从可信来源提取DLL到程序目录等安全方式即可解决。掌握这类运行库修复思路,也能应对MSVCR71.DLL、MFC71.DLL等缺失问题,让老旧财务软件、工业工具和单机游戏重新正常启动。
Pandas数据清洗与可视化全流程实战:从Excel脏数据到分析结论
数据处理是数据分析的基石,而Pandas作为Python生态中最核心的数据分析库,为数据清洗、转换与聚合提供了高效且可复用的解决方案。面对业务部门提供的Excel销售明细,常见问题包括重复订单、格式混乱的日期、缺失金额以及混用符号的数值字段。通过Pandas的DataFrame结构,可以系统化地完成去重、缺失值处理、类型转换与列名规范化,进而利用groupby和pivot_table实现多维度的业务规律探索。在可视化阶段,借助matplotlib与seaborn配置中文字体后,可快速输出月度趋势折线图与品类排名条形图。这一套工作流不仅适用于电商销售场景,也可泛化至金融、运营等任何需要从原始表格中提炼洞察的领域。掌握Pandas的清洗与聚合技巧,能将一次性的手工操作沉淀为可复跑的自动化管道,显著提升数据分析效率。
双重检查锁(DCL)为什么必须加volatile?从一次线上事故说起
在Java并发编程中,如何优雅地实现线程安全的单例模式,一直是开发者关注的焦点。懒汉式延迟加载虽能避免资源浪费,但多线程环境下容易产生多个实例,而简单的synchronized同步又会带来性能损耗。双重检查锁(DCL)通过两次判空与volatile关键字的配合,在保证线程安全的同时最大程度降低锁竞争,成为面试与工程实践中的经典方案。从JMM指令重排序到类加载机制,理解DCL的每一个细节,是深入掌握Java并发原理的关键一步。在实际项目中,无论是配置中心、数据库连接池,还是工具类的全局状态管理,DCL都提供了延迟加载与性能之间的平衡。同时涵盖线上事故、反射与序列化攻防场景,全面拆解双重检查锁的演进、实现与替代方案。
边缘网关中数据面与控制面的解耦设计与工程实践
工业物联网场景下,边缘网关承担着数据采集与设备控制的双重角色。随着设备规模扩大和采样频率提升,遥测数据与控制指令混跑在同一链路,容易因TCP队头阻塞导致反控指令延迟甚至丢失。解决之道在于将数据面、控制面与运维通道在逻辑上解耦:数据面负责高频遥测,允许主动丢弃;控制面保障指令可靠投递,配合去重、幂等与回执机制;运维通道则提供加密远程维护能力。通过应用层优先级队列、DSCP服务质量标记及Linux tc流量整形,可在单张SIM卡链路上划分出独立车道,确保拥塞时控制报文优先通过。该方案已在多个工业现场验证,能有效降低反控延迟并提升系统鲁棒性,适合边缘计算盒子及物联网采集器架构参考。
Elasticsearch基础查询语法详解:从Query DSL到生产环境避坑指南
在数据检索领域,如何高效地从海量数据中精准定位目标信息,是搜索、日志分析等场景的核心挑战。Elasticsearch(ES)作为主流的分布式搜索引擎,其查询语法 Query DSL 基于 JSON 定义了一套灵活的表达方式。理解查询上下文与过滤上下文的本质区别,是掌握 ES 查询性能优化的第一步:match 查询经过分析器处理,适合全文检索;term 查询用于 keyword 字段的精确匹配。bool 复合查询中的 must、filter、should、must_not 各有其适用场景,合理设计能平衡召回率与相关性排序。此外,range 范围查询、聚合分析以及深分页处理,也是工程实践中的高频操作。掌握这些基础语法,能够帮助开发者构建稳定高效的搜索服务,并规避因字段映射不当、滥用通配符等引发的生产环境性能陷阱,最终从“会写查询”走向“懂 ES”。
Koopman模型预测控制:全局线性化让非线性MPC快两个数量级
在工业控制中,非线性系统的模型预测控制(MPC)常因在线求解非线性规划而面临计算瓶颈。Koopman算子理论通过将非线性动力学提升到高维线性空间,使预测模型全局线性化,从而将优化问题转化为标准二次规划。这种基于数据驱动的建模方式,不仅保留了非线性特征,还大幅提升了求解速度。本文以倒立摆为例,从字典函数设计、数据采集、Koopman矩阵拟合到MPC控制器搭建,完整演示了在Matlab中的实现流程,并讨论了状态估计与工程落地要点。该方法适用于机器人、无人车等强非线性场景,为工程实践提供了一种高效、可维护的控制方案。
Shell脚本实战:批量创建用户并设置密码的完整方案
Linux系统管理中,用户管理是运维的基础操作,面对新员工入职、考试系统初始化等场景,手动逐条执行useradd和passwd不仅耗时,还容易出错。Shell脚本作为自动化利器,能够将重复性操作封装为高效流程。其核心原理是利用命令的非交互特性:useradd创建用户,chpasswd通过标准输入批量设置密码,避免了passwd交互式卡顿。结合密码策略(如强制首次登录修改密码)与用户组规划,可构建安全合规的账号体系。在实际工程中,批量创建用户脚本常结合文本清单驱动,支持导入、日志记录和幂等重跑。本文基于真实运维经验,以批量创建用户并设置密码为目标,从需求设计到命令选型,给出可直接改用的完整Shell实现,并覆盖批量删除与权限扩展场景,帮助读者摆脱手动建号的低效与风险。
JavaScript+Node.js实现微博自动化发布:从登录态到接口调用的完整实战
自动化发布是Web开发与运维场景中的高频需求,其底层依赖HTTP请求、会话管理与接口调用三大基础能力。在JavaScript技术栈中,Node.js凭借异步I/O与丰富的生态,成为实现脚本化操作的首选工具。理解Cookie的获取、校验与热更新机制,是构建稳定自动化任务的核心前提;而请求频率控制与错误重试策略,则决定了工具能否从“跑通”走向“可长期运行”。这类技术常用于内容分发、定时提醒、多平台同步等场景,能有效替代重复手工操作。当目标平台为微博时,开发者还需掌握其发布接口的参数构造、图片上传链路及风控应对逻辑。本文以JavaScript为语言基础,结合Node.js环境,从登录态管理、接口请求构造到任务队列封装,系统梳理微博自动化发布的工程化实现路径,帮助开发者快速落地一个可靠、可维护的发布工具。
虚拟化高可用与灾备实战:集群搭建、故障转移与备份恢复
服务器虚拟化通过资源池化提升硬件利用率,但单机部署仍存在单点故障风险。高可用集群利用心跳检测与隔离机制实现虚拟机故障转移,是保障业务连续性的关键。数据备份则通过全量、增量等策略应对逻辑错误与误删,支撑快速恢复。异地灾备进一步解决机房级灾难,通过复制与切换降低RPO与RTO。这些技术适用于运维环境从测试转向生产、核心业务迁移上云的场景。从虚拟化到高可用,再到灾备体系,需分层规划并反复演练,才能真正落地。
KindEditor文档中CAD图纸批量提取与转存全流程指南
在工程文档管理中,CAD图纸常常以图片或附件形式嵌入富文本编辑器生成的HTML中,而KindEditor作为常见的网页编辑器,并不具备图纸解析能力。要高效完成图纸归集,核心在于用脚本对正文HTML进行结构化解析,准确提取img标签、附件链接和base64内嵌图片。通过Python与BeautifulSoup等常规工具,可将图片类图纸与DWG/DXF文件分路转存,并配合版本转换、批量命名和回写更新,形成一条可追溯的工程资产管理链路。该方法适用于制造文档换版、图库迁移等高频场景,能够大幅减少人工下载与重绘成本。本文还针对转存后新装CAD打开图纸“满屏是线”的常见现象,给出从硬件加速、线宽显示到重复对象清理的排查步骤,助力图纸交付更好落地。
已经到底了哦