MaxCompute支持通过deleteupdate操作,在行级别删除或更新Transactional表中的数据。该功能处于公测阶段,对数据的更改存在不可回退性,如果您需要体验该功能,请提前通过select+insert操作将数据备份至其他表中。目前deleteupdate处于公测阶段,不收取deleteupdate语句的计算费用,其他查询Transactional表的作业仍会产生计算费用。公测期间不保证此功能可以用于正式生产,请您做好相关数据备份。

前提条件

执行deleteupdate操作前需要具备目标Transactional表的读取表数据权限(Select)及更新表数据权限(Update)。授权操作请参见授权

功能介绍

MaxCompute的deleteupdate功能具备与传统数据库用法类似的删除或更新表中指定行的能力。

实际使用deleteupdate功能时,系统会针对每一次删除或更新操作自动生成用户不可见的Delta文件,用于记录删除或更新的数据信息。具体实现原理如下:
  • delete:Delta文件中使用txnid(bigint)rowid(bigint)字段标识Transactional表的Base文件(表在系统中的存储形式)中的记录在哪次删除操作中被删除。

    例如,表t1的Base文件为f1,且内容为a, b, c, a, b,当执行delete from t1 where c1='a';命令后,系统会生成一个单独的f1.delta文件。假设txnidt0,则f1.delta的内容是((0, t0), (3, t0)),标识行0和行3,在txnt0中被删除了。如果再执行一次delete操作,系统会又生成一个f2.delta文件,该文件仍然是根据Base文件f1编号,读取文件时,基于Base文件f1和当前所有Delta文件的共同表示结果,读取没有被删除的数据。

  • updateupdate操作会转换为delete+insert into的实现逻辑。
deleteupdate功能具备的优势如下:
  • 读写数据量下降

    此前,MaxCompute通过insert intoinsert overwrite操作方式删除或更新表数据,更多信息,请参见插入或覆写数据(INSERT INTO | INSERT OVERWRITE)。当用户需要更新表或分区中的少量数据时,如果通过insert操作实现,需要先读取表的全量数据,然后通过select操作更新数据,最后通过insert操作将全量数据写回表中,效率较低。使用deleteupdate功能后,系统无需全量读取数据,读写数据量会显著下降。

  • 可直接读取最新状态的表

    此前,MaxCompute在批量更新表数据场景使用的是拉链表,该方式需要在表中增加start_dateend_date辅助列,标识某一行记录的生命周期。当查询表的最新状态时,系统需要从大量数据中根据时间戳获取表的最新状态,使用不够直观。使用deleteupdate功能后,系统可以基于表的Base文件和Delta文件的共同表示结果,直接读取最新状态的表。

注意 多次deleteupdate操作会使Transactional表的底层存储增大,会提高存储和后续查询费用,且影响后续查询效率,建议定期合并(Compact)后台数据。更多合并操作信息,请参见合并Transactional表文件

当作业并发运行且操作的目标表相同时,可能会出现作业冲突问题,更多信息,请参见ACID语义

应用场景

deleteupdate功能适用于随机、低频删除或更新表或分区中的少量数据。例如,按照T+1周期性地批量对表或分区中5%以下的行删除或更新数据。

deleteupdate功能不适用于高频更新、删除数据或实时写入目标表场景。

使用限制

deleteupdate功能及对应Transactional表的使用限制如下:
  • 仅支持Transactional表。更多创建Transactional表信息,请参见表操作
  • 在创建表时,不支持将聚簇表、外部表设置为Transactional表。
  • 不支持MaxCompute内部表、外部表、聚簇表与Transactional表互转。
  • 不支持其他系统的作业(例如MaxCompute Spark、PAI、Graph)访问Transactional表。
  • 不支持clone tablemerge partition操作。
  • 在对Transactional表的重要数据执行updatedeleteinsert overwrite操作前需要手动通过select+insert操作将数据备份至其他表中。

注意事项

通过deleteupdate操作删除或更新表或分区内的数据时,注意事项如下:
  • 如果删除或更新操作次数较多,或写入和后续读数据的频率非常频繁,建议使用deleteupdate操作,并且在多次执行删除或更新操作之后,请合并表的Base文件和Delta文件,降低表的实际存储。更多信息,请参见合并Transactional表文件
  • 如果删除或更新行数较多(超过5%)并且操作不频繁,但后续对该表的读操作比较频繁,建议使用insert overwriteinsert into操作。更多信息,请参见插入或覆写数据(INSERT INTO | INSERT OVERWRITE)

    例如,某业务场景为每次删除或更新10%的数据,一天更新10次。建议根据实际情况评估deleteupdate操作产生的费用及后续对读性能的消耗是否小于每次使用insert overwriteinsert into操作产生的费用及后续对读性能的消耗,比较两种方式在具体场景中的效率,选择更优方案。

  • 删除数据会生成Delta文件,所以删除数据不一定能降低存储,如果您希望通过delete操作删除数据来降低存储,请合并表的Base文件和Delta文件,降低表的实际存储。更多信息,请参见合并Transactional表文件

删除数据(DELETE)

delete操作用于删除Transactional分区表或非分区表中满足指定条件的单行或多行数据。

  • 命令格式
    delete from <table_name> [where <where_condition>];
  • 参数说明
    • table_name:必填。待执行delete操作的Transactional表名称。
    • where_condition:可选。WHERE子句,用于筛选满足条件的数据。更多WHERE子句信息,请参见WHERE子句(where_condition)。如果不带WHERE子句,会删除表中的所有数据。
  • 使用示例
    • 示例1:创建非分区表acid_delete,并导入数据,执行delete操作删除满足指定条件的行数据。命令示例如下:
      --创建Transactional表acid_delete。
      create table if not exists acid_delete(id bigint) tblproperties ("transactional"="true"); 
      
      --插入数据。
      insert overwrite table acid_delete values(1),(2),(3),(2); 
      
      --查看插入结果。
      select * from acid_delete; 
      
      +------------+
      | id         |
      +------------+
      | 1          |
      | 2          |
      | 3          |
      | 2          |
      +------------+
      
      --删除id为2的行,如果在MaxCompute客户端(odpscmd)执行,需要输入yes|no确认。
      delete from acid_delete where id = 2; 
      
      --查看结果表中数据只有1、3。
      select * from acid_delete; 
      
      +------------+
      | id         |
      +------------+
      | 1          |
      | 3          |
      +------------+
    • 示例2:创建分区表acid_delete_pt,并导入数据,执行delete操作删除满足指定条件的行。命令示例如下:
      --创建Transactional表acid_delete_pt。 
      create table if not exists acid_delete_pt(id bigint) partitioned by(ds string) tblproperties ("transactional"="true");
      
      --添加分区。
      alter table acid_delete_pt add if not exists partition (ds= '2019');
      alter table acid_delete_pt add if not exists partition (ds= '2018');
      
      --插入数据。
      insert overwrite table acid_delete_pt partition (ds='2019') values(1),(2),(3);
      insert overwrite table acid_delete_pt partition (ds='2018') values(1),(2),(3);
      
      --查看插入结果。
      select * from acid_delete_pt;
      
      +------------+------------+
      | id         | ds         |
      +------------+------------+
      | 1          | 2018       |
      | 2          | 2018       |
      | 3          | 2018       |
      | 1          | 2019       |
      | 2          | 2019       |
      | 3          | 2019       |
      +------------+------------+
      
      --删除分区为2019且id为2的数据,如果在MaxCompute客户端(odpscmd)执行,需要输入yes|no确认。
      delete from acid_delete_pt where ds='2019' and id = 2;
      
      --查看结果表中已删除分区为2019且id为2的数据。
      select * from acid_delete_pt;
      
      +------------+------------+
      | id         | ds         |
      +------------+------------+
      | 1          | 2018       |
      | 2          | 2018       |
      | 3          | 2018       |
      | 1          | 2019       |
      | 3          | 2019       |
      +------------+------------+
    • 示例3:创建目标表acid_delete_t和关联表acid_delete_s,通过关联操作删除满足指定条件的行。命令示例如下:
      --创建目标Transactional表acid_delete_t和关联表acid_delete_s。
      create table if not exists acid_delete_t(id int,value1 int,value2 int) tblproperties ("transactional"="true");
      create table if not exists acid_delete_s(id int,value1 int,value2 int);
      
      --插入数据。
      insert overwrite table acid_delete_t values(2,20,21),(3,30,31),(4,40,41);
      insert overwrite table acid_delete_s values(1,100,101),(2,200,201),(3,300,301);
      
      --删除acid_delete_t表中id与acid_delete_s表中id不匹配的行。如果在MaxCompute客户端(odpscmd)执行,需要输入yes|no确认。
      delete from acid_delete_t where not exists (select * from acid_delete_s where acid_delete_t.id=acid_delete_s.id);
      
      --查看结果表中只有id为2、3的数据。
      select * from acid_delete_t;
      
      +------------+------------+------------+
      | id         | value1     | value2     |
      +------------+------------+------------+
      | 2          | 20         | 21         |
      | 3          | 30         | 31         |
      +------------+------------+------------+

更新数据(UPDATE)

update操作用于将Transactional分区表或非分区表中行对应的单列或多列数据更新为新值。

  • 命令格式
    update <table_name> set <col1_name> = <value1> [, <col2_name> = <value2> ...] [where <where_condition>];
    update <table_name> set (<col1_name> [, <col2_name> ...]) = (<value1> [, <value2> ...])[where <where_condition>];
  • 参数说明
    • table_name:必填。待执行update操作的Transactional表名称。
    • col1_namecol2_name:至少更新一个。待修改行对应的列名称。
    • value1value2:至少更新一个列值。修改后的新值。
    • where_condition:可选。WHERE子句,用于筛选满足条件的数据。更多WHERE子句信息,请参见WHERE子句(where_condition)。如果不带WHERE子句,会更新表中的所有数据。
  • 使用示例
    • 示例1:创建非分区表acid_update,并导入数据,执行update操作更新满足指定条件的行对应的列数据。命令示例如下:
      --创建Transactional表acid_update。  
      create table if not exists acid_update(id bigint) tblproperties ("transactional"="true");
      
      --插入数据。
      insert overwrite table acid_update values(1),(2),(3),(2);
      
      --查看插入结果。
      select * from acid_update; 
      
      +------------+
      | id         |
      +------------+
      | 1          |
      | 2          |
      | 3          |
      | 2          |
      +------------+
      
      --将所有id为2的行,id值更新为4。
      update acid_update set id = 4 where id = 2; 
      
      --查看更新结果,2被更新为4。
      select * from acid_update; 
      
      +------------+
      | id         |
      +------------+
      | 1          |
      | 3          |
      | 4          |
      | 4          |
      +------------+
    • 示例2:创建分区表acid_update,并导入数据,执行update操作更新满足指定条件的行对应的列数据。命令示例如下:
      --创建Transactional表acid_update_pt。 
      create table if not exists acid_update_pt(id bigint) partitioned by(ds string) tblproperties ("transactional"="true");
      
      --添加分区。
      alter table acid_update_pt add if not exists partition (ds= '2019');
      
      --插入数据。
      insert overwrite table acid_update_pt partition (ds='2019') values(1),(2),(3);
      --查看插入结果
      select * from acid_update_pt where ds = '2019';
      
      +------------+------------+
      | id         | ds         |
      +------------+------------+
      | 1          | 2019       |
      | 2          | 2019       |
      | 3          | 2019       |
      +------------+------------+
      
      --更新指定行的一列数据,将分区为2019的所有id=2的行,id值更新为4。
      update acid_update_pt set id = 4 where ds = '2019' and id = 2; 
      
      --查看更新结果,2被更新为4。
      select * from acid_update_pt where ds = '2019';
      
      +------------+------------+
      | id         | ds         |
      +------------+------------+
      | 4          | 2019       |
      | 1          | 2019       |
      | 3          | 2019       |
      +------------+------------+
    • 示例3:创建目标表acid_update_t和关联表acid_update_s,实现同时更新多列值。命令示例如下:
      --创建待更新目标Transactional表acid_update_t和关联表acid_update_s。
      create table if not exists acid_update_t(id int,value1 int,value2 int) tblproperties ("transactional"="true");
      create table if not exists acid_update_s(id int,value1 int,value2 int);
      
      --插入数据。
      insert overwrite table acid_update_t values(2,20,21),(3,30,31),(4,40,41);
      insert overwrite table acid_update_s values(1,100,101),(2,200,201),(3,300,301);
      --方式一:用常量更新。
      update acid_update_t set (value1, value2) = (60,61);
      --查询方式一目标表结果数据。
      select * from acid_update_t;
      
      +------------+------------+------------+
      | id         | value1     | value2     |
      +------------+------------+------------+
      | 2          | 60         | 61         |
      | 3          | 60         | 61         |
      | 4          | 60         | 61         |
      +------------+------------+------------+
      
      --方式二:关联更新,规则为acid_update_t表左关联acid_update_s表。
      update acid_update_t set (value1, value2) = (select value1, value2 from acid_update_s where acid_update_t.id = acid_update_s.id);
      --查询方式二目标表结果数据。
      select * from acid_update_t;
      
      +------------+------------+------------+
      | id         | value1     | value2     |
      +------------+------------+------------+
      | 2          | 200        | 201        |
      | 3          | 300        | 301        |
      | 4          | NULL       | NULL       |
      +------------+------------+------------+
      
      --方式三:关联更新,规则为增加过滤条件,只更新交集。
      update acid_update_t set (value1, value2) = (select value1, value2 from acid_update_s where acid_update_t.id = acid_update_s.id) where acid_update_t.id in (select id from acid_update_s);
      --查询方式三目标表结果数据。
      select * from acid_update_t;
      +------------+------------+------------+
      | id         | value1     | value2     |
      +------------+------------+------------+
      | 4          | 40         | 41         |
      | 2          | 200        | 201        |
      | 3          | 300        | 301        |
      +------------+------------+------------+
      
      --方式四:用汇总结果关联更新。
      update acid_update_t set (id, value1, value2) = (select id, max(value1),max(value2) from acid_update_s where acid_update_t.id = acid_update_s.id group by acid_update_s.id) where acid_update_t.id in (select id from acid_update_s);
      --查询方式四目标表结果数据。
      select * from acid_update_t;
      
      +------------+------------+------------+
      | id         | value1     | value2     |
      +------------+------------+------------+
      | 4          | 40         | 41         |
      | 2          | 200        | 201        |
      | 3          | 300        | 301        |
      +------------+------------+------------+

合并Transactional表文件

Transactional表底层物理存储为不支持直接读取的Base文件和Delta文件。对Transactional表执行updatedelete操作,不会修改Base文件,只会追加Delta文件,所以会出现更新或删除次数越多,表实际占用存储越大的情况,多次累积的Delta文件会产生较高的存储和后续查询费用。

对同一表或分区,执行多次updatedelete操作,会生成较多Delta文件。系统读数据时,需要加载这些Delta文件来确定哪些行被更新或删除,较多的Delta文件会影响数据读取效率。此时您可以将Base文件和Delta合并,减少存储以便提升数据读取效率。

  • 命令格式
    alter table <table_name> [partition (<partition_key> = '<partition_value>' [, ...])] compact {minor|major};
  • 参数说明
    • table_name:必填。待合并文件的Transactional表名称。
    • partition_key:可选。当Transactional表为分区表时,指定分区列名。
    • partition_value:可选。当Transactional表为分区表时,指定分区列名对应的列值。
    • major|minor:至少选择其中一个。二者的区别是:
      • minor:只将Base文件及其下所有的Delta文件合并,消除Delta文件。
      • major:不仅将Base文件及其下所有的Delta文件合并,消除Delta文件,还会把表对应的Base文件中的小文件进行合并。当Base文件较小(小于32 MB)或有Delta文件的情况下,等价于重新对表执行insert overwrite操作,但当Base文件足够大(大于等于32 MB ),且不存在Delta文件的情况下,不会重写。
  • 使用示例
    • 示例1:基于Transactional表acid_delete,合并表文件。命令示例如下:
      alter table acid_delete compact minor;
      返回结果如下:
      Summary:
      Nothing found to merge, set odps.merge.cross.paths=true if cross path merge is permitted.
      OK
    • 示例2:基于Transactional表acid_update_pt,合并表文件。命令示例如下:
      alter table acid_update_pt partition (ds = '2019') compact major;
      返回结果如下:
      Summary:
      table name: acid_update_pt /ds=2019  instance count: 2  run time: 6
        before merge, file count:        8  file size: 2613  file physical size: 7839
         after merge, file count:        2  file size: 679  file physical size: 2037
      
      OK

常见问题

问题现象:执行update操作时,报错ODPS-0010000:System internal error - fuxi job failed, caused by: Data Set should contains exactly one row

问题原因:待更新的行数据与子查询结果中的数据无法一一对应,系统无法判断对哪一行数据进行更新。命令示例如下:
update store set (s_county, s_manager) = (select d_country, d_manager from store_delta sd where sd.s_store_sk = store.s_store_sk) where s_store_sk in (select s_store_sk from store_delta);
通过子查询select d_country, d_manager from store_delta sd where sd.s_store_sk = store.s_store_sk与store_delta关联,并用store_delta的数据更新store。假设store的s_store_sk中有[1, 2, 3]三行数据,如果store_delta的s_store_sk有[1, 1]两行数据,数据无法一一对应,执行报错。

解决措施:确保待更新的行数据与子查询结果中的数据一一对应。