PostgreSQL数组实战:增删改查与GIN索引优化全解析
做后端开发这些年接触过不少数据库MySQL、Oracle、SQL Server 都折腾过但真要让我选一个“日常顺手、功能耐打”的数据库我会毫不犹豫选 PostgreSQL。尤其是它的数组类型很多人眼里可能只是“能存个列表”的小功能实际用好了能在不少场景里直接省掉一张关联表或者砍掉一段又臭又长的 JSON 解析代码。这篇就从增删改查和索引优化两条线把 PostgreSQL 数组的实战细节完整过一遍适合正在用 PG 做业务开发、想提升查询性能、或者准备面试时被问到“数组索引怎么优化”的朋友参考。PostgreSQL 的数组不是花架子它是完整的一等公民类型可以建表、可以加索引、可以参与聚合、可以和普通列一样做条件过滤。但恰恰因为它太灵活很多人要么不敢用要么用了之后写查询时总踩坑。我见过不少同事把数组当成“可以存放多个值的字符串”来用存的时候拿逗号拼查的时候用 LIKE 去匹配最后性能惨不忍睹还怪数据库不行。问题不在 PostgreSQL在于没有用对姿势。这篇文章会把数组的完整操作链路拆开告诉你每一步该怎么做、为什么要这么做、以及怎么让数组查询真正跑出索引的效果。1. 先搞清楚数组到底适合解决什么问题1.1 数组不是用来炫技的它解决的是“一行多值”的需求业务里“一行对应多个值”的需求太常见了。一篇文章有多个标签、一个用户有多个角色、一个订单有多个物流轨迹点、一个商品有多个轮播图 ID。传统关系型数据库的第一反应是拆一张子表文章表和标签表做关联查询时 JOIN 一把。这种设计没问题完全符合关系模型理论数据规范化程度也高但实际用起来有几个让你难受的地方一是查询要走 JOINSQL 变复杂尤其当主表数据量大、标签表也需要过滤时优化器稍不留神就给你整出嵌套循环或者排序合并慢得让人抓狂。二是应用层要处理的对象从“一行记录”变成了“一行记录加一个子集合”ORM 映射起来特别别扭很多框架的关联查询懒加载能把你坑到怀疑人生。三是如果只是单纯想把多值存下来比如一组数字 ID、一组短字符串拆表有点杀鸡用牛刀的意思。数组类型在这里的价值就是把“多值”直接塞进一行里查询时只要关注主表不需要 JOIN不需要子查询也不需要额外的应用层拼装逻辑。一条记录就是一个完整对象干净利落。1.2 数组、JSONB、关联表到底怎么选这不是一个非此即彼的问题而是要看你的数据形态和访问模式。数组最适合的场景是元素类型固定、长度相对稳定、访问模式基本是“整存整取”或者“判断是否存在某个值”。比如标签text[]、ID 列表bigint[]、权限码集合int[]这类数据用数组非常顺手。JSONB 适合的是结构不固定、嵌套层级深、需要存储复杂文档的场景比如用户扩展属性、接口请求日志、配置快照。如果你只用 JSONB 存一个扁平列表其实是用大炮打蚊子JSONB 的解析开销、存储膨胀都比数组高不少。关联表则适合数据需要独立维护、需要和其他表做复杂 JOIN、需要分页展示子表、需要针对子记录做频繁单点更新。如果有一天你发现自己在对数组做“查找并修改某个元素的属性”那基本说明该拆表了。我个人的选型习惯是只读或近只读的集合用数组结构灵活多变用 JSONB需要强一致性和复杂关联用子表。这不是教条而是经过项目验证的取舍逻辑——数组牺牲了一部分规范化换来了查询路径的极简和性能上的确定性。1.3 数组的类型体系与存储形态PostgreSQL 数组支持几乎所有基础类型int、bigint、text、varchar、numeric、date、timestamp甚至自定义复合类型。最常用的是 int[] 和 text[]前者适合存 ID 集合后者适合存标签、名称集合。还可以有多维数组比如 int[][]但实际业务里用得不多而且多维数组在索引优化上没有太多发挥空间后面会专门说限制。数组在存储层本质上就是一个变长类型内部按二进制格式保存元素序列。你看到的花括号字面量 {1,2,3} 只是外部表示内部并不是文本。索引可以建立在数组列上元素可以被高效检索这让数组不再只是“存储容器”而是一个可被数据库引擎深度优化的数据类型。2. 数组的增删改查实操全流程2.1 建表数组字段的定义与默认值建表定义数组列很简单直接在类型名后面加方括号。下面这个例子是内容管理系统中很典型的场景一篇文章可以打多个标签同时记录每天的浏览量CREATE TABLE articles ( id bigserial PRIMARY KEY, title text NOT NULL, tags text[] DEFAULT {}, view_counts int[] DEFAULT ARRAY[0, 0], created_at timestamptz DEFAULT now() );DEFAULT {}是空数组字面量DEFAULT ARRAY[0, 0]是构造器表达式。两个写法等价都能给数组字段提供默认值。这里有一个新手容易犯的错空数组{}不是 NULL它是一个长度为 0 的数组。默认值设置为空数组能避免后续查询里到处写COALESCE。如果你希望数组存储时保证元素唯一PostgreSQL 原生数组没有唯一性约束只能靠应用层或者触发器保证。如果非常需要唯一性intarray 扩展能提供一些辅助函数但整体上数组不是一个强约束容器别把它当 Set 用。2.2 插入数据三种写法各有适用场景数组插入有三种主流写法我建议你全部掌握因为不同场景下它们各有优势。第一种是字面量写法INSERT INTO articles (title, tags) VALUES (PG数组实战, {postgresql,数组,索引优化});字面量用花括号包裹元素之间用逗号分隔。这种写法简洁适合 SQL 脚本、数据迁移、手工维护场景但缺点是如果元素里包含特殊字符比如逗号、反斜杠、花括号就必须加转义阅读性会比较差。第二种是 ARRAY 构造器INSERT INTO articles (title, tags) VALUES (PG数组实战, ARRAY[postgresql, 数组, 索引优化]);ARRAY 构造器把元素当成普通 SQL 常量处理不需要担心花括号字面量的转义问题参数化查询也更好写。应用层往数据库传数组时用这条最方便比如 JDBC 或 pg 驱动直接传 Java 数组。第三种是从查询结果聚合INSERT INTO articles (title, tags) SELECT PG数组实战, array_agg(tag_name) FROM tag_table WHERE article_id 100;这种方式在数据回填、历史数据迁移时非常实用。把子查询里的多行结果聚合到一个数组直接插入目标表省去应用层循环查询再拼接的步骤。array_agg还可以配ORDER BY控制元素顺序比如array_agg(tag_name ORDER BY tag_name)。2.3 查询下标、切片与包含判断数组查询是整篇文章的重头戏因为大多数索引优化问题都出在这一环节。按下标取元素PostgreSQL 数组的下标从 1 开始这一点和 Java、JavaScript、Python 都不一样是新手最容易踩的坑。取第一个标签SELECT title, tags[1] FROM articles WHERE id 1;如果下标越界返回结果是 NULL 而不是报错这点要注意tags[10] 对一个只有 3 个元素的数组来说结果是 NULL不会异常。这在应用层取值时容易造成“看起来没数据”的错觉。切片获取子数组SELECT title, tags[1:2] FROM articles WHERE id 1;切片返回的是一个新数组包含原数组第 1 到第 2 个元素。切片的边界可以省略比如tags[:2]表示从开头到第 2 个元素tags[2:]表示从第 2 个元素到结尾。切片的边界写错会返回 NULL比如tags[3:2]这种下界大于上界的写法返回的是空数组还是 NULL 取决于具体边界这条细节建议你本地实测一次心里有个数。包含判断 操作符这是数组查询里最核心的操作符。表示“左边的数组是否包含右边数组的所有元素”。比如查询包含 postgresql 标签的文章SELECT * FROM articles WHERE tags ARRAY[postgresql];如果 tags 为 {postgresql, 数组, 索引优化}这个条件成立。 的顺序是“左边大集合、右边小集合”千万别写反。写反了就是“右边的数组是否包含左边的数组”语义完全不同而且也会影响索引匹配。重叠判断 操作符如果想查标签中任意一个匹配就可以用操作符SELECT * FROM articles WHERE tags ARRAY[postgresql, 运维];语义是“两个数组是否有交集”只要有一个元素相同就返回 true。这个操作符也是走 GIN 索引的。长度与维度array_length(tags, 1)返回第一维的长度cardinality(tags)返回元素总数。对一维数组来说两者一致对多维数组cardinality 返回所有维度的元素乘积。判断空数组更推荐用cardinality(tags) 0或者直接tags {}但要注意 NULL 与空数组的区别。搜索是否包含某个元素另一种常见写法有些老 DBA 习惯用position(tag in tags)或者tags ARRAY[tag]前者是字符串思维不适配数组。记住PostgreSQL 里数组的元素匹配就用它背后是倒排索引和位图扫描性能比任何形式的遍历都要强。2.4 修改数据整数组替换、单元素更新、函数式修改数组的更新分为三种粒度对应三条不同的 SQL。整体替换把标签列整个换成新数组UPDATE articles SET tags ARRAY[数据库, PostgreSQL] WHERE id 1;这种方式最直接适合“用户在前端编辑了整个标签列表提交后覆盖存储”的场景。单元素更新直接按下标更新某个位置UPDATE articles SET tags[1] PG实战 WHERE id 1;如果下标越界PostgreSQL 会自动扩展数组中间空缺的位置用 NULL 填充。比如 tags 原来是 3 个元素你更新 tags[5]结果数组长度变成 5中间第 4、5 个元素中第 5 个有值第 4 个是 NULL。这个行为有好有坏坏处是你可能不小心制造出带空洞的数组后续查询长度和遍历时都要注意。函数式修改追加、删除、拼接元素用数组函数-- 追加一个元素 UPDATE articles SET tags array_append(tags, 实战) WHERE id 1; -- 追加到数组头部 UPDATE articles SET tags array_prepend(实战, tags) WHERE id 1; -- 删除所有等于指定值的元素 UPDATE articles SET tags array_remove(tags, 临时标签) WHERE id 1; -- 替换指定元素 UPDATE articles SET tags array_replace(tags, 旧值, 新值) WHERE id 1; -- 拼接两个数组 UPDATE articles SET tags array_cat(tags, ARRAY[新增1, 新增2]) WHERE id 1;array_append和array_cat的区别是前者追加一个元素后者拼接整个数组。元素数量多时array_cat更高效因为底层可以走一次内存分配。这里有个很重要的性能认知数组字段的任何更新在 PostgreSQL 里都是整行重写。因为 PostgreSQL 的 MVCC 机制决定了更新会产生新版本行数组列作为变长字段也会被整体写入新版本。也就是说即使你只改了 tags[1]数据库也把整行数据复制了一份。理解了这一点就知道数组不应该用于高频单点更新的场景。2.5 删除数据清空数组与删除字段删除数组列中的部分元素用array_remove即可UPDATE articles SET tags array_remove(tags, 不再需要的标签) WHERE id 1;清空整个数组把它重置为空数组UPDATE articles SET tags {} WHERE id 1;把数组字段置为 NULLUPDATE articles SET tags NULL WHERE id 1;注意{}和NULL的区别{}是一个空数组array_length查询返回 0和判断都返回 false但 IS NULL 判断返回 false而NULL是“没有这个值”IS NULL返回 true返回 NULL。在 WHERE 条件里NULL 的包含判断会被当成 false 处理这点在数据清理时很容易搞混。如果彻底不需要这个字段了用 DDL 删除ALTER TABLE articles DROP COLUMN tags;数组的删除不像关联表那么灵活它无法做到“删除第一个满足条件的元素”这种精细操作array_remove会删除所有等于目标值的元素。如果需要更细粒度的操作比如只删除第一个匹配项你需要先把数组 unnest 成行处理完再聚合回去这个后面会说。3. 数组索引优化GIN 索引是如何起飞的3.1 为什么 B-tree 索引对数组使不上劲关系型数据库最常用的 B-tree 索引对数组列几乎是无能为力的。原因在于 B-tree 的索引结构建立在“有序比较”之上它能高效处理等值查询和范围查询比如id 5、create_date 2024-01-01。但数组列内部是一组无序的集合你要查询的不是“整个数组等于某个数组”而是“数组内部是否有某个元素”。传统 B-tree 索引在这种场景下只能做到全数组匹配比如SELECT * FROM articles WHERE tags ARRAY[postgresql, 数组];这种全等值查询 B-tree 能帮上忙但实际业务里这种需求少之又少。更常见的需求是“包含某个标签”“与某组标签有交集”这时候 B-tree 只能老老实实全表扫描。有些新手尝试给数组列建 B-tree 索引然后发现 EXPLAIN 里完全没有走索引就是因为没搞清楚 B-tree 的定位。3.2 GIN 索引的原理倒排思想在数据库里的落地GINGeneralized Inverted Index索引的底层是倒排索引这名字听着高级思路其实非常简单。想象一本技术书的书末索引它不是按页码罗列内容而是把每个关键词映射到它出现的所有页码。GIN 做的事情类似把数组里的每个元素提取出来为每个元素记录它出现在哪些行里。当你执行tags ARRAY[postgresql]时GIN 索引可以立刻定位到所有包含 postgresql 这个元素的行然后通过位图把符合条件的结果集返回。这个过程的复杂度和数组长度无关只和这个元素匹配的行数有关所以查询性能非常稳定。GIN 的代价在写入端每次插入或更新一行需要把数组中的所有元素都拆开逐个更新倒排索引项。所以 GIN 索引会让写入变慢但换来了查询的巨大提升。这正好对应数组的最佳使用场景——低频写入、高频查询。GIN 默认的数组操作符类是array_ops支持 包含、重叠、被包含、 数组整体相等等操作符。日常用的最多的是 和 。3.3 创建索引语法与操作符类的选择对 text[] 的 tags 列建 GIN 索引CREATE INDEX idx_articles_tags ON articles USING gin (tags);对 int[] 列建 GIN 索引可以先用 intarray 扩展CREATE EXTENSION IF NOT EXISTS intarray;intarray 扩展提供了专门针对 int4、int8 数组的gin__int_ops操作符类优势是索引体积更小、构建更快还额外支持、这些操作符的优化。创建索引时指定CREATE INDEX idx_articles_view_counts ON articles USING gin (view_counts gin__int_ops);这里有个容易忽略的细节intarray 的gin__int_ops在处理包含 NULL 元素的数组时行为与默认的array_ops不同如果你无法保证数组元素不为 NULL用默认操作符类更稳妥。我的建议是除非你明确知道数据里不会有 NULL否则先用默认array_ops等性能确实成为瓶颈再切换优化。3.4 实测从全表扫描到索引扫描光说不练假把式我用一个实际的例子展示索引的效果。假设 articles 表有 20 万行数据每行 tags 平均有 4 个元素。没有 GIN 索引时执行包含查询EXPLAIN ANALYZE SELECT * FROM articles WHERE tags ARRAY[postgresql];计划里通常出现Seq Scan on articles (cost0.00..10000.00 rows1000 width...) (actual time0.05..85.32 rows9600 loops1) Filter: (tags {postgresql}::text[])全表扫描一条条过滤耗时 85 毫秒左右。数据量涨到 200 万行时这个数字会线性增长到数百毫秒甚至秒级。创建索引后再次执行同样的查询CREATE INDEX idx_articles_tags ON articles USING gin (tags); ANALYZE articles; EXPLAIN ANALYZE SELECT * FROM articles WHERE tags ARRAY[postgresql];计划变为Bitmap Index Scan on idx_articles_tags (cost0.00..40.20 rows9600 width0) (actual time0.35..0.35 rows9600 loops1) Index Cond: (tags {postgresql}::text[]) Bitmap Heap Scan on articles (cost40.20..800.00 rows9600 width...) (actual time0.40..1.20 rows9600 loops1)先走 Bitmap Index Scan 定位行号再回表取数据整体耗时降到 2 毫秒以内。20 万行数据看起来提升不算夸张但把量级放大到千万行全表扫描基本不可用GIN 索引依然能稳定在几十毫秒内返回结果。这个量级下的差异就是线上能用和不能用的区别。4. 避坑指南数组查询为什么有时候不听话4.1 下标查询走不了索引改用包含判断最典型的坑就是很多人会写SELECT * FROM articles WHERE tags[1] postgresql;这想表达的是“第一个标签是 postgresql”这个语义本身没问题但它完全没法使用 GIN 索引因为 GIN 索引存储的是每个元素到行号的映射而不是“下标位置”。你告诉数据库的是“按位置取元素再比较”数据库只能逐行取出数组再判断。如果你真正想要的是“标签里包含 postgresql”应该改成SELECT * FROM articles WHERE tags ARRAY[postgresql];语义上有一点差异前者要求第一个元素精确匹配后者只要求某个元素匹配。但大多数业务场景要的是后者。这里的关键认知是想让查询走 GIN 索引条件必须操作“数组作为集合”的层面而不是数组内部的具体下标。4.2 函数包裹导致索引失效另一个高频踩坑是把数组列包在函数里SELECT * FROM articles WHERE array_length(tags, 1) 10;这种查询在索引设计上几乎没有直接方案。array_length是函数调用GIN 索引不会自动感知“某个数组长度大于 10”这种条件。想优化这类查询比较实用的做法是在应用层或写入时单独维护一个长度字段或者用表达式索引CREATE INDEX idx_articles_tag_len ON articles USING gin ((array_length(tags, 1)));但是表达式索引只能处理精确的等值或范围匹配而且维护成本和收益往往不成正比。更推荐的做法是把“数组长度”这类高频过滤属性显式建模为独立列单独加普通 B-tree 索引这是最省心也最可预测的方式。同样的问题还出现在模糊匹配上。比如SELECT * FROM articles WHERE tags ARRAY[%postgres%];这个写法不会把你想要的模糊匹配效果跑出来。是全值匹配%postgres% 是一个普通字符串它只会匹配数组中恰好等于 %postgres% 的元素。要对数组元素做模糊查询先把数组展开成行再配合 LIKE 和 pg_trgm 扩展实现SELECT DISTINCT a.* FROM articles a CROSS JOIN LATERAL unnest(a.tags) AS tag WHERE tag LIKE %postgres%;这条 SQL 的缺点是 unnest 之后没法继续使用 GIN 索引只适合数据量可控的场景。真正需要数组模糊搜索且数据量很大时更合理的选择是把标签拆成关联表用 pg_trgm 做 GIN 索引。这也是我反复强调的数组适合“整存整取精确包含”模糊搜索不是它的主场。4.3 空数组、NULL 元素和包含判断的坑空数组和 NULL 的区分前面讲过这里再补充一个实际开发中的翻车案例。有一个统计接口需要统计所有带标签的文章数SQL 写成SELECT count(*) FROM articles WHERE tags ARRAY[];结果查出来的总是 0因为数组里根本没有空字符串元素。过滤空数组的正确姿势是SELECT count(*) FROM articles WHERE cardinality(tags) 0;或者SELECT count(*) FROM articles WHERE tags {};NULL 元素也容易造成诡异行为。如果数组里有 NULL 元素比如{postgresql,NULL,数组}执行SELECT * FROM articles WHERE tags ARRAY[NULL]::text[];结果是未知NULL在 WHERE 条件里等同于 false。这不是数据库的 bug而是 SQL 三值逻辑的必然结果。如果你需要过滤“包含 NULL 元素的数组”只能使用array_position(tags, NULL) IS NOT NULL这类偏门写法本质上还是函数遍历逃不开全表扫描。4.4 数组字段更新时的索引维护成本GIN 索引的写入代价比 B-tree 高得多这一点在设计表结构时就必须想清楚。每个数组元素都会更新倒排索引如果一个数组平均有 10 个元素每次插入或更新这行数据GIN 索引就要做 10 次索引项维护。业务里如果有大量高频写入操作比如日志流水、订单快照给数组字段建 GIN 索引很可能导致写入性能大幅下降。我踩过的坑是一个“标签点击统计表”每天定时更新标签数组几十万行数据建了 GIN 索引后一次全量更新任务从 3 分钟涨到 15 分钟。排查后发现瓶颈不在 SQL而在 GIN 索引的维护。后来把标签数组改成 JSONB 字段利用 jsonb 的 GIN 索引特性配合部分场景优化才把更新时间压了回去。所以建索引前建议做一次成本评估如果这个表的业务读多写少GIN 是神器如果读写比接近 1:1 或者写更多建议谨慎或者只在只读副本上建索引。5. 高频问题排查与操作速查5.1 问题一明明建了 GIN 索引 查询还是全表扫描遇到这个问题的概率比想象中高尤其是新手。先检查三件事。第一表的数据量是不是太小。优化器对只有几千行的表往往会选择全表扫描因为走索引的随机 IO 成本比顺序扫描还要高这属于优化器的正常判断并不是索引没生效。你可以用SET enable_seqscan off强制走索引对比一下但生产环境不要这么干。第二查询条件是不是被函数包裹了。比如WHERE array_to_string(tags, ,) LIKE %postgresql%这是把数组当字符串处理GIN 索引完全不认识这种写法。必须改成操作符。第三操作符类不匹配。如果你建索引时用了gin__int_ops但查询时用 text[]类型都不一致索引自然帮不上忙。可以用\d index_name查看索引的操作符类确认它的适用范围。第四表很长时间没做 ANALYZE统计信息太旧导致优化器误判。执行ANALYZE articles;再试一次很多时候问题就这么简单。5.2 问题二unnest 之后数据行数变多或丢失空元素unnest把数组展开成多行是很多人爱用的技巧但它有认知陷阱。原本一行记录展开后有多个标签展开后行数会变多如果不加 DISTINCTJOIN 结果可能重复。另外如果数组里有 NULL 元素unnest 照样会输出一行 NULL和“没有值”语义不同过滤时容易漏数据。我的经验是需要“一行对一个数组元素”做查询时优先考虑LEFT JOIN LATERAL unnest(...)它可以保证主表的行不丢失即使数组为空也能返回一行主表数据。想展开后再聚合回去用array_agg配合 GROUP BY。举个典型场景要把文章表和用户行为表做关联行为表里记录的是 tag_id想让一篇文章关联到所有匹配的 tag_id 并汇总。一次性把文章 tags 展开SELECT a.id, u.user_id, count(*) FROM articles a JOIN user_actions u ON u.tag_id ANY(a.tags) GROUP BY a.id, u.user_id;这里的ANY(a.tags)避免了显式 unnest 和 join 重复也是数组用法里容易被忽略的一个隐藏技能。5.3 问题三同学从 0 开始的下标引发的越界连环坑我从 0 开始编排数组下标时第一次取 tags[0] 得到的是 NULL然后拿着 NULL 去做了业务判断排查了半天才发现是下标问题。PostgreSQL 数组下标从 1 开始是硬性规定无法修改。解决办法有两个一是强行规定应用层所有数组访问从 1 开始前端展示时单独处理二是使用array_lower和array_upper动态取边界但更推荐前者因为动态取边界会写出很啰嗦的 SQL。5.4 数组操作速查表我把常用的数组操作整理成一张表方便贴在手边操作SQL 示例是否走 GIN 索引取第 n 个元素tags[1]否取切片tags[1:2]否包含所有元素tags ARRAY[a,b]是有任意交集tags ARRAY[a,b]是被包含tags ARRAY[a,b]是数组相等tags ARRAY[a,b]是GIN 支持长度判断cardinality(tags) 0否判断元素是否为 NULLtags[1] IS NULL否追加元素array_append(tags, c)否写操作移除元素array_remove(tags, a)否写操作展开成行unnest(tags)否但可配合其它索引聚合回数组array_agg(val)否写操作这张表最大的价值是帮你在写 SQL 前快速判断这条查询能不能走索引。凡是下标、函数、长度相关的基本都要在心里打个问号。5.5 一个常用的性能优化技巧数组列上的部分索引有一种业务场景值得聊一聊不是对所有数组过滤都建 GIN 索引而是针对高频查询值建部分索引。比如业务只关心“状态标签”为 active 的记录CREATE INDEX idx_articles_active_tag ON articles USING gin (tags) WHERE tags ARRAY[active];这样索引只维护包含 active 标签的行索引体积大幅减小写入性能也有改善。你甚至可以针对不同类型的标签分别建部分索引查询时优化器会自动匹配。这个技巧是我在广告系统里用过的标签总量千万级但活跃标签就几十个分类部分索引让存储开销降到了原来的三分之一。6. 最后分享一点项目里的实践体会在实际项目里我用数组最多的地方是内容标签聚合、批量 ID 透传和权限位图简化。我的体会是数组字段适合“读取频繁、写入低频”的场景一旦你的数组被频繁增加删除、或者元素数量膨胀到几十上百个还是老老实实拆关联表更稳妥。索引再快也顶不住无休止的整行更新加索引重建。相反如果业务基本是写入后只读数组加 GIN 索引的组合确实又简洁又高效。还有一个可以直接拿去用的小技巧如果要从关联表往数组字段里回填数据一条 UPDATE 加array_agg子查询就能搞定千万别在应用层循环挨个 UPDATE。把集合操作思维从应用层搬到 SQL 层你会发现很多疑难杂症其实根本不存在。

相关新闻

H5西瓜币圈完整运营版搭建教程:从环境配置到代理推广闭环

H5西瓜币圈完整运营版搭建教程:从环境配置到代理推广闭环

简介:H5西瓜币圈完整运营版本是一套基于HTML5的数字货币交易系统完整运营资源包,面向有建站需求的开发者、运营者及代理推广从业者,解决快速部署虚拟货币交易平台与搭建代理分销体系的问题。压缩包约138.32MB,包含PHP源码、SQL数据…

2026/10/9 3:48:22 阅读更多 →
Claude Opus 5.5 焚诀实战:CLAUDE.md 与 Sub-agent 编排指南

Claude Opus 5.5 焚诀实战:CLAUDE.md 与 Sub-agent 编排指南

1. 这次“焚诀”到底更新了什么:从标题拆解到核心变化“焚诀”这个词在圈子里其实是个戏称,指的是那种一旦用上就回不去、算力烧得心疼但产出质量高到离谱的配置组合。这次 Claude Opus 5.5 被冠上“最新焚诀”,核心不是模型本身跑分涨了多少…

2026/10/9 3:47:21 阅读更多 →
Orbtile:用Logitech键盘实现AI编码代理实时意图感知

Orbtile:用Logitech键盘实现AI编码代理实时意图感知

1. 这不是另一个“状态灯”,而是一套实时协作意图感知系统我第一次在 Hacker News 上看到 “Show HN: Orbtile – See which AI coding agent needs you, on a Logitech keypad” 这个标题时,下意识划了过去——又一个把 RGB 灯带接上键盘的玩具项目&…

2026/10/9 3:47:21 阅读更多 →

最新新闻

学Simulink——基于反电动势过零检测的直流无刷电机(BLDC)无感控制仿真

学Simulink——基于反电动势过零检测的直流无刷电机(BLDC)无感控制仿真

目录 手把手教你学Simulink——基于反电动势过零检测的直流无刷电机(BLDC)无感控制仿真 一、 引言:当“霍尔传感器”成为过去式——反电动势过零检测如何成就真正的“无感”BLDC? 二、 问题本质:反电动势过零的“物理机制”与“协同逻辑” 1. 核心物理机制 2. 协同逻辑…

2026/10/9 5:35:40 阅读更多 →
Agent执行错操作怎么办——从权限确认、沙箱隔离到幂等回滚的安全执行实战

Agent执行错操作怎么办——从权限确认、沙箱隔离到幂等回滚的安全执行实战

Agent 安全执行不是“多弹确认框”,而是把权限、隔离、验证与恢复做成一条系统闭环。 AI Agent | 智能体安全 | 权限控制 | Human-in-the-Loop | 沙箱隔离 | 最小权限 | 幂等性 | 回滚机制 | Guardrails | MCP安全 当 Agent 从“给建议”升级到“真正执行动作”,系统…

2026/10/9 5:35:40 阅读更多 →
多Agent成本失控实战——并发、上下文与Token预算如何系统治理 从“Agent越多越强”到“每一美元都能解释”

多Agent成本失控实战——并发、上下文与Token预算如何系统治理 从“Agent越多越强”到“每一美元都能解释”

多Agent成本失控实战——并发、上下文与Token预算如何系统治理从“Agent越多越强”到“每一美元都能解释”#多Agent #Agent #LLM #Token #成本优化 #上下文工程 #Prompt Caching #并发控制 #模型路由 #可观测性多Agent系统真正昂贵的地方,往往不是…

2026/10/9 5:35:40 阅读更多 →
context-mode:基于上下文感知的终端环境自动切换

context-mode:基于上下文感知的终端环境自动切换

1. 为什么需要 context-mode:从手动切换走向规则切换1.1 配置切换的痛点,可能只有折腾过的人才懂每天要在三四种工作场景里来回切换:白天在项目仓库里写业务代码,下午翻开几个开源项目的源码做研究,晚上可能还要写自己…

2026/10/9 5:35:40 阅读更多 →
Meson 0.53.0 新特性深度解析:fs 模块、动态链接器选择与构建配置摘要实战

Meson 0.53.0 新特性深度解析:fs 模块、动态链接器选择与构建配置摘要实战

构建工具 【免费下载链接】meson The Meson Build System 项目地址: https://gitcode.com/gh_mirrors/me/meson 点击查看 免费下载 本篇文章以 Meson 0.53.0 官方发布说明(Release-notes-for-0.53.0.md)为主体,逐项解析该版本引入…

2026/10/9 5:35:40 阅读更多 →
HARA与风险评估方法

HARA与风险评估方法

EPS electronic power steering, 电子助力转向系统 发现了问题,下面就要制定措施 内容来源 : https://www.bilibili.com/video/BV1GdeQ6xEHi?spm_id_from333.788.videopod.sections&vd_source473185c2a7a9b79ef8fcea7dce5ca501

2026/10/9 5:34:40 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 13:34:55 阅读更多 →