Doris 开启 Partial Update:实现不存在就插入,存在就更新,NULL 不更新原值

这篇文章用一个测试表完整跑通 Doris 的Partial Column Update(部分列更新)

  • 不存在就插入
  • 存在就只更新指定列
  • 值为NULL时不覆盖原值(保持原值)

1. 先搞懂:Partial Update 的前提条件

1.1 必须是 Unique Key 表,并且是 Merge-on-Write(MoW)

Doris 的Unique Key表天然支持 Upsert:同一个 Key 写入多次,只保留“最新结果”。

Partial Update(只更新部分列)INSERT来说,要求 Unique Key 表开启 Merge-on-Write

注意:如果你的旧表是 Merge-on-Read(MoR),很多情况下不能在线改成 MoW,会报类似 “Can not change UNIQUE KEY to Merge-On-Write mode”。
这种情况通常需要建新表迁移(后面会简单提一下思路)。


2. 创建一个测试表(支持 Partial Update)

我们创建一个简单测试表:用户画像/配置类数据很常见。

CREATETABLEtest_partial_upsert(uidBIGINT,name STRING,ageINT,city STRING,scoreINT,update_timeDATETIME)UNIQUEKEY(uid)DISTRIBUTEDBYHASH(uid)BUCKETS4PROPERTIES("enable_unique_key_merge_on_write"="true");

解释一下:

  • UNIQUE KEY(uid):以uid作为主键(唯一键)
  • enable_unique_key_merge_on_write=true:开启 MoW,后续才能用INSERT做 partial update

3. 开启 Partial Update:必须设置的两个变量

Doris 用INSERT做部分列更新,需要先设置会话变量:

SETenable_unique_key_partial_update=true;

官方文档说明:这个变量默认是false,不开就不允许用INSERT做部分列更新。

另外,为了实现“key 不存在就插入”,通常还要关闭 strict(严格)模式:

SETenable_insert_strict=false;

原因:严格模式下,导入/写入对不符合规则的数据会过滤或报错;在 partial update + 新 key 场景下,很多同学会碰到“被过滤/不生效”的情况,因此实践里常配合关闭 strict。

小提示:这些SET会话级别的,只对当前连接生效。生产环境里建议在应用拿到连接后统一设置(不要每条 SQL 都 SET)。


4. 不存在就插入,存在就更新(Upsert)

4.1 第一次写入:插入一整行

SETenable_unique_key_partial_update=true;SETenable_insert_strict=false;INSERTINTOtest_partial_upsert(uid,name,age,city,score,update_time)VALUES(1,'Alice',30,'Shanghai',80,'2026-01-07 10:00:00');

表里现在有:

  • uid=1, name=Alice, age=30, city=Shanghai, score=80

4.2 第二次写入:只更新部分列(Partial Update)

比如我只想更新scoreupdate_time,其它列保持不动:

SETenable_unique_key_partial_update=true;SETenable_insert_strict=false;INSERTINTOtest_partial_upsert(uid,score,update_time)VALUES(1,95,'2026-01-07 10:05:00');

结果:uid=1 那行会变成

  • name 仍然是 Alice(没动)
  • age、city 也不变
  • score 更新为 95

这就是Partial Update:只更新你写进列清单里的列。

4.3 如果 key 不存在:同样会插入新行

INSERTINTOtest_partial_upsert(uid,score,update_time)VALUES(2,60,'2026-01-07 10:06:00');

uid=2 不存在,就会插入一条新行(其它列为 NULL 或默认值,取决于表定义)。


5. 重点来了:如何做到「值为 NULL 就不更新原值」?

5.1 先说结论:Doris 目前不会自动忽略 NULL

在 Partial Update 模式下:

  • 只要某列出现在INSERT的列清单里
  • 且对应值是NULL
  • Doris 就会把目标列更新成NULL

也就是说,“NULL 不覆盖原值”不是默认行为。社区也有人提了增强需求,希望提供“忽略 NULL”的选项。

5.2 正确做法:NULL 的列不要出现在 INSERT 列清单里

例如你希望:

  • city 传 NULL 时不更新
  • score 有值才更新

写法是:当 city 为 NULL,就别把 city 写进 SQL

-- 只更新 score,不包含 cityINSERTINTOtest_partial_upsert(uid,score,update_time)VALUES(1,100,'2026-01-07 10:10:00');

这样city就会保持原值。

错误示例(会把 city 覆盖成 NULL):

INSERTINTOtest_partial_upsert(uid,city,score)VALUES(1,NULL,100);

6. 应用层怎么写:推荐两种方式

方式 A(最简单):按场景拆成多条 SQL

比如你有“更新分数”“更新城市”“更新年龄”几类场景,每个场景写固定列的INSERT

  • 需要更新的列写进去
  • 不需要的列不写进去
  • 永远不会把 NULL 覆盖到别的列

优点:最稳、最容易排查
缺点:SQL 会多一些

方式 B(更通用):MyBatis 动态 SQL(NULL 就不拼列)

这就是你们现在在做的方向。核心思想:
参数为 NULL → 不拼接该列,从而避免覆盖原值。

示例(通用 partial upsert mapper):

<insertid="upsertPartial"parameterType="com.demo.TestPojo">INSERT INTO test_partial_upsert (uid<iftest="name != null">, name</if><iftest="age != null">, age</if><iftest="city != null">, city</if><iftest="score != null">, score</if><iftest="updateTime != null">, update_time</if>) VALUES (#{uid}<iftest="name != null">, #{name}</if><iftest="age != null">, #{age}</if><iftest="city != null">, #{city}</if><iftest="score != null">, #{score}</if><iftest="updateTime != null">, #{updateTime}</if>)</insert>

这样:city=null时就不会写 city 列,自然也不会更新成 NULL。


7. 批量写入

你可能想把多条数据做成:

INSERTINTOtest_partial_upsert(uid,score)VALUES(1,10),(2,20),(3,30);

但动态 SQL 有一个天然矛盾:
批量 INSERT 要求每一行的列集合完全一致
如果 list 里有些对象city=null,有些对象city!=null,那列集合就不一致,不能一条 SQL 全塞进去。

正确做法按“列集合”分组,再分别批量 INSERT
思路:

  1. 给每个对象算一个 mask(哪些列非空)
  2. mask 相同的放一组
  3. 每组用一条固定列集合的批量 INSERT

1、mask 设计:用 bit 表示“本组要写哪些列”

我们固定 key 列必须写:uid
其它列:name/age/city/score/update_time可能为 null,希望 null 不更新 → 用 mask 控制它是否出现在 SQL 中。

publicfinalclassTestMask{publicstaticfinalintNAME=1<<0;publicstaticfinalintAGE=1<<1;publicstaticfinalintCITY=1<<2;publicstaticfinalintSCORE=1<<3;publicstaticfinalintUPDATE_TIME=1<<4;privateTestMask(){}}

计算 mask:

publicstaticintmask(TestPartialUpsertPojop){intm=0;if(p.getName()!=null)m|=TestMask.NAME;if(p.getAge()!=null)m|=TestMask.AGE;if(p.getCity()!=null)m|=TestMask.CITY;if(p.getScore()!=null)m|=TestMask.SCORE;if(p.getUpdateTime()!=null)m|=TestMask.UPDATE_TIME;returnm;}

解释:如果city=null,mask 不包含 CITY,这一批 SQL 就不会出现 city 列 →不会把数据库里的 city 改成 NULL


2、Java:分组 + 每组再按 500 分批写入

@ServicepublicclassTestPartialUpsertService{privatefinalTestPartialUpsertMappermapper;publicTestPartialUpsertService(TestPartialUpsertMappermapper){this.mapper=mapper;}@TransactionalpublicvoidupsertBatch(List<TestPartialUpsertPojo>list){if(list==null||list.isEmpty())return;// 1) 重要:同一连接里开启 session 变量(不要塞到 insert SQL 里)mapper.enablePartialUpdate();mapper.disableInsertStrict();// 2) 按 mask 分组Map<Integer,List<TestPartialUpsertPojo>>groups=list.stream().collect(Collectors.groupingBy(TestPartialUpsertService::mask));// 3) 每组做批量 INSERT(避免 SQL 太长,500 一批)for(Map.Entry<Integer,List<TestPartialUpsertPojo>>e:groups.entrySet()){intm=e.getKey();List<TestPartialUpsertPojo>g=e.getValue();for(inti=0;i<g.size();i+=500){List<TestPartialUpsertPojo>chunk=g.subList(i,Math.min(i+500,g.size()));mapper.upsertByMask(m,chunk);}}}privatestaticintmask(TestPartialUpsertPojop){intm=0;if(p.getName()!=null)m|=TestMask.NAME;if(p.getAge()!=null)m|=TestMask.AGE;if(p.getCity()!=null)m|=TestMask.CITY;if(p.getScore()!=null)m|=TestMask.SCORE;if(p.getUpdateTime()!=null)m|=TestMask.UPDATE_TIME;returnm;}}

3、MyBatis Mapper:三件事

3.1接口

@MapperpublicinterfaceTestPartialUpsertMapper{// session 变量:同一连接生效intenablePartialUpdate();intdisableInsertStrict();// 按 mask + 批量 upsertintupsertByMask(@Param("mask")intmask,@Param("list")List<TestPartialUpsertPojo>list);}

3.2 Pojo

publicclassTestPartialUpsertPojo{privateLonguid;privateStringname;privateIntegerage;privateStringcity;privateIntegerscore;privateLocalDateTimeupdateTime;// getter/setter 省略}

4、MyBatis XML:根据 mask 决定列集合(固定列集合 + foreach VALUES)

4.1 session SET(单独写,避免多语句 insert)

<updateid="enablePartialUpdate">SET enable_unique_key_partial_update = true</update><updateid="disableInsertStrict">SET enable_insert_strict = false</update>

4.2 核心:按 mask 生成“统一列集合”的批量 INSERT

<insertid="upsertByMask">INSERT INTO test_partial_upsert (uid<iftest="(mask & 1) != 0">, name</if><iftest="(mask & 2) != 0">, age</if><iftest="(mask & 4) != 0">, city</if><iftest="(mask & 8) != 0">, score</if><iftest="(mask & 16) != 0">, update_time</if>) VALUES<foreachcollection="list"item="x"separator=",">(#{x.uid}<iftest="(mask & 1) != 0">, #{x.name}</if><iftest="(mask & 2) != 0">, #{x.age}</if><iftest="(mask & 4) != 0">, #{x.city}</if><iftest="(mask & 8) != 0">, #{x.score}</if><iftest="(mask & 16) != 0">, #{x.updateTime}</if>)</foreach></insert>

为什么这个 XML 能保证“NULL 不更新原值”?

  • mask决定列是否出现
  • 例如 mask 没有 CITY,那么整条 SQL根本不包含city
  • Doris partial update 只会更新 SQL 中出现的列
  • 所以city保持原值

5、调用示例(验证效果)

List<TestPartialUpsertPojo>list=newArrayList<>();// uid=1:只更新 scorelist.add(newTestPartialUpsertPojo(1L,null,null,null,100,LocalDateTime.now()));// uid=2:更新 city + agelist.add(newTestPartialUpsertPojo(2L,null,28,"Shanghai",null,LocalDateTime.now()));// uid=3:更新 name(其余 null 不更新)list.add(newTestPartialUpsertPojo(3L,"Alice",null,null,null,null));service.upsertBatch(list);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1125805.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker快速部署docker私有仓库

前言 记录docker快速部署docker私有仓库命令 docker部署私有仓库 1. 创建认证密码文件 安装htpasswd工具 # CentOS/RHEL centos执行这个 yum install -y httpd-tools # Ubuntu/Debian apt-get install -y apache2-utils 2. 创建认证目录 mkdir -p /zero/registry/auth sudo…

【确认出席】卢勇 上海市数商协会秘书长丨上海·1月14日

第八届金猿论坛嘉宾“本次大会&#xff0c;现场将会举行十年先锋人物、十年标杆产品、CIO、数据要素价值释放、AI Infra领先企业、创新技术、Data Agent创新应用、国产化优秀代表厂商八项大奖的“第八届金猿季颁奖典礼”欢迎报名参与&#xff0c;观礼见证。大数据产业创新服务媒…

AI+敏捷时代,专项测试人员是否还有存在的必要?

一、PO 程序员 AI 能否覆盖全部测试需求&#xff1f;1. PO&#xff08;产品负责人&#xff09;的角色PO关注的是业务价值和用户需求&#xff0c;通过用户故事表达功能期望。虽然PO会参与验收&#xff08;UAT&#xff09;&#xff0c;但通常不具备系统性测试思维&#xff0c;也…

成为一名优秀的AI产品经理:2025年AI产品经理必备:大模型产品经理终极学习路线图,一篇就够了!

成为一名优秀的AI产品经理&#xff0c;需要具备深厚的技术背景、良好的产品直觉、敏锐的市场洞察力以及出色的沟通协调能力。以下是一份详尽的AI产品经理学习路线&#xff0c;旨在帮助有意进入该领域的学习者建立起坚实的基础&#xff0c;并逐步成长为行业内的专家。 一、基础知…

口碑好的无轨平车哪家好

口碑好的无轨平车哪家好在工业领域&#xff0c;无轨平车作为一种重要的物料运输设备&#xff0c;其质量和口碑备受关注。那么&#xff0c;口碑好的无轨平车哪家好呢&#xff1f;杭州龙立智能科技值得重点关注。卓越的技术实力杭州龙立智能科技在无轨平车的研发上投入了大量精力…

计算机提示“解析软件包时出现问题”怎么解决?别慌,小白也能看懂的修复指南

你是否也遇到过这种情况&#xff1a;好不容易下载了一个几百兆的游戏或应用APK安装包&#xff0c;满心欢喜点击安装时&#xff0c;屏幕却冷冰冰地弹出一行字——“解析软件包时出现问题”。这一刻简直让人心态爆炸&#xff01;别急着删文件&#xff0c;这通常不是手机坏了&…

AtomicBoolean 作用

AtomicBoolean 是 Java 并发包 (java.util.concurrent.atomic) 里的一个“线程安全布尔”。 一句话&#xff1a;它就是一个 可以安全地被多线程同时读/写的布尔值&#xff0c;而且比直接用 synchronized 或 volatile 自己加锁更轻量、更快。为什么需要它 普通 boolean 在多线程…

新时代的国防动员系统——人机环境生态体系

新时代的国防动员系统正由“人力密集型”向“科技密集型”跃升&#xff0c;其核心抓手是构建“人—机—环境”深度融合的生态体系&#xff0c;实现平战一体、全域联动、智能高效的动员能力。综合近期实践与政策指向&#xff0c;可将其体系架构概括为“一条主线、三大支柱、N类场…

Build in Public,才是普通人的 AI 之路

凌晨两点&#xff0c;写完最后一行代码&#xff0c;我习惯性地打开社交媒体&#xff0c;记录下今天解决的那个棘手bug和思路&#xff0c;十分钟后&#xff0c;评论区出现了几个同样遭遇此问题的程序员&#xff0c;我们开始了一场深夜技术交流。大家好&#xff0c;这里是程序员晚…

二进制重构嵌入(Binary Reconstructive Embedding)压缩函数实现详解

前言 在无监督哈希方法中,Binary Reconstructive Embedding(BRE)是一种经典的基于重构误差最小化的算法。它通过学习一组二进制嵌入,使得数据在汉明空间中的距离能够尽可能保留原始欧氏空间的结构,同时最小化二进制码对原始数据的重构误差。这种方法在保持简单高效的同时…

印巴的“0”与美委的“0”

印巴空战与美委之战中的“零”&#xff0c;分别代表了现代战争中两种不同维度的“零”理念——印巴空战的“零战损”彰显了体系化空战的优势&#xff0c;美委之战的“零伤亡”则凸显了不对称作战的效能&#xff0c;二者均对现代战争形态具有重要启示意义&#xff0c;值得深入关…

和谐哈希(Harmonious Hashing)学习算法详解

和谐哈希(Harmonious Hashing,简称HamH)是一种高效的无监督哈希学习方法,通过结合主成分分析(PCA)和正交旋转优化,在低维空间中生成均衡的二进制码。这种方法确保各比特位携带独立且平衡的信息,避免传统PCA哈希中可能出现的比特冗余或方差不均问题,非常适合高维数据的…

MATLAB实现谱哈希(Spectral Hashing)编码函数详解

谱哈希(Spectral Hashing)编码函数在MATLAB中的实现与解析 谱哈希(Spectral Hashing,简称SH)是一种经典的无监督哈希方法,它通过对数据进行拉普拉斯特征映射(Laplacian Eigenmaps)的谱分析,学习一组正弦函数组合来生成二进制码。这种方法的核心思想是将哈希函数设计为…

人-AI协同体系的构建

人-AI协同体系是一种以“人-机-环境”三元主体动态共生为核心的复杂系统&#xff0c;其“态、势、感、知”的协同框架是实现高效、自适应智能的关键。这一框架并非孤立的模块&#xff0c;而是通过状态共享、能力互补、环境感知、知识融合的闭环&#xff0c;推动人机从“工具式分…

MBA必看!9个降AIGC工具推荐,高效应对AI检测

MBA必看&#xff01;9个降AIGC工具推荐&#xff0c;高效应对AI检测 AI降重工具&#xff1a;高效应对AI检测的关键武器 在当前学术写作中&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;率的控制已经成为MBA学生必须面对的重要课题。随着高校和期刊对AI检测技术的不断…

Manus 爆火之后,我梳理了现在最火的 10 大 AI 智能体

如果你最近关注 AI 圈&#xff0c;大概率已经刷到一条重磅消息&#xff1a; Manus 被 Meta 收购了。外媒给出的价格区间在 20—30 亿美元之间&#xff0c;综合多方信源&#xff0c;25 亿美元几乎已经是业内共识。但说实话&#xff0c;这件事真正值得反复琢磨的地方&#xff0c;…

力扣96 不同的二叉搜索树 java实现

96.不同的二叉搜索树给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。示例 1&#xff1a;输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a;输入&#xff1a;n 1 输出…

【评委确认】蔡超 泰佩思琦数字化与技术副总裁丨第八届年度金猿榜单/奖项评审团专家

终审评委专家团成员 “【提示】2025第八届年度金猿颁奖典礼将在上海举行&#xff0c;此次榜单/奖项的评选依然会进行初审、公审、终审&#xff08;上述专家评审&#xff09;三轮严格评定&#xff0c;并会在国内外渠道大规模发布传播欢迎申报。 大数据产业创新服务媒体 ——聚焦…

二分法排查:通过禁用模块或数据分段定位

技术文章大纲&#xff1a;Bug悬案侦破大会引言简述软件开发中Bug的普遍性和复杂性引入“悬案”概念&#xff1a;难以复现、逻辑隐蔽或跨系统的疑难问题提出通过协作、工具和方法论高效解决问题的思路Bug悬案的典型特征难以复现&#xff08;如偶发性并发问题&#xff09;依赖特定…

144本!计算机人工智能领域SCI汇总

本期&#xff0c;小编给大家汇总了一下人工智能领域&#xff08;ARTIFICIAL INTELLIGENCE&#xff09;的144本SCI期刊合集&#xff0c;仅供各位投稿参考&#xff01;完整目录如下&#xff1a;来源&#xff1a;SciencePub学术整理注&#xff1a;厚台备注“人工智能”&#xff0c…