【问题标题】:How to establish read-only-once implement within SAP HANA?如何在 SAP HANA 中建立只读一次的实现?
【发布时间】:2020-10-29 19:01:14
【问题描述】:

背景:我是一名长期的 MSSQL 开发人员...我想知道的是如何从 SAP HANA 实现只读一次选择。

高级伪代码:

  1. 通过 db proc(查询)收集请求
  2. 通过请求调用 API
  3. 存储请求(响应)的结果

我有一个表 (A),它是流程输入的来源。进程完成后,会将结果写入另一个表 (B)。

如果我只是在表 A 中添加一列以避免并发处理器从 A 中选择相同的记录,也许这一切都解决了?

我想知道如何在不将列添加到源表 A 的情况下执行此操作。

我尝试的是表 A 和 B 之间的左外连接,以从 A 中获取在 B 中没有(尚未)对应行的行。这不起作用,或者我没有实现这样的行被处理任何处理器仅 1 次。

我有一个存储过程来处理批量选择:

/*
 *      getBatch.sql
 *
 *      SYNOPSIS:  Retrieve the next set of criteria to be used in a search
 *                 request.  Use left outer join between input source table
 *                 and results table to determine the next set of inputs, and
 *                 provide support so that concurrent processes may call this
 *                 proc and get their inputs exclusively.
 */
alter procedure "ACOX"."getBatch" (
     in in_limit int
    ,in in_run_group_id varchar(36)
    ,out ot_result table (
         id bigint
        ,runGroupId varchar(36)
        ,sourceTableRefId integer
        ,name nvarchar(22)
        ,location nvarchar(13)
        ,regionCode nvarchar(3)
        ,countryCode nvarchar(3)
    )
) language sqlscript sql security definer as
begin       

    -- insert new records:
    insert into "ACOX"."search_result_v4" (
         "RUN_GROUP_ID"
        ,"BEGIN_DATE_TS"
        ,"SOURCE_TABLE"
        ,"SOURCE_TABLE_REFID"   
    )
    select
         in_run_group_id as "RUN_GROUP_ID"
        ,CURRENT_TIMESTAMP as "BEGIN_DATE_TS"
        ,'acox.searchCriteria' as "SOURCE_TABLE"
        ,fp.descriptor_id as "SOURCE_TABLE_REFID"
    from 
        acox.searchCriteria fp
    left join "ACOX"."us_state_codes" st
        on trim(fp.region) = trim(st.usps)
    left outer join "ACOX"."search_result_v4" r
        on fp.descriptor_id = r.source_table_refid
    where
        st.usps is not null
        and r.BEGIN_DATE_TS is null
    limit :in_limit;
    
    -- select records inserted for return:
    ot_result =
    select
         r.ID id
        ,r.RUN_GROUP_ID runGroupId
        ,fp.descriptor_id sourceTableRefId
        ,fp.merch_name name
        ,fp.Location location
        ,st.usps regionCode
        ,'USA' countryCode
    from 
        acox.searchCriteria fp
    left join "ACOX"."us_state_codes" st
        on trim(fp.region) = trim(st.usps)
    inner join "ACOX"."search_result_v4" r
        on fp.descriptor_id = r.source_table_refid
        and r.COMPLETE_DATE_TS is null
        and r.RUN_GROUP_ID = in_run_group_id
    where
        st.usps is not null
    limit :in_limit;

end;

当运行 7 个并发处理器时,我得到了 35% 的重叠。也就是说,在 5,000 个输入行中,生成的行数为 6,755。运行时间约为 7 分钟。

目前我的解决方案包括向源表添加一列。我想避免这种情况,但它似乎是一个更简单的实现。我将很快更新代码,但它在插入之前包含一个更新语句。

有用的参考资料:

【问题讨论】:

  • 7 分钟。处理 5000 条记录太慢了。不知道为什么需要在“search_results_v4”表中实际存储数据,因为这似乎是一个临时的记录列表。
  • @LarsBr。我更新了我的问题以提供此时间的背景信息。 Db 有 API 调用请求的来源。存储响应以供最新分析。 API 调用时间是最大的成本。

标签: sql hana hana-sql-script


【解决方案1】:

首先:在任何 RDBMS 中都没有“只读-一次”,包括 MS SQL。 从字面上看,这意味着给定的记录只能被读取一次,然后对于所有后续读取都会“消失”。 (这实际上是队列的作用,或者队列的著名特殊情况:管道)

我认为这不是您想要的。

相反,我相信您希望实现类似于“一次且仅一次”又名“精确一次”消息传递的处理语义。虽然这是 impossible 在潜在的分区网络中实现的,但它可以在数据库的事务上下文中实现。

这是一个常见的要求,例如批量数据加载作业应该只加载迄今为止尚未加载的数据(即在最后一次批量加载作业开始后创建的数据)。

对于冗长的前置文本感到抱歉,但任何解决方案都取决于我们是否清楚我们想要实际实现的目标。我现在会找到一种方法。

主要的 RDBMS 早就发现,如果目标是实现高事务吞吐量,那么阻塞读取器通常是一个糟糕的主意。因此,HANA 不会阻止读者 - 永远(好吧,永远不会,但在正常操作设置中)。 “exactly-once”处理要求的主要问题实际上不是读取记录,而是处理不止一次或根本不处理的可能性。

这两个潜在问题都可以通过以下方法解决:

  1. SELECT ... FOR UPDATE ... 应处理的记录(基于例如未处理的记录、最多 N 条记录、奇偶 ID、邮政编码……)。这样,当前会话就有了一个 UPDATE TRANSACTION 上下文和选定记录上的独占锁。其他事务仍然可以读取这些记录,但没有其他事务可以锁定这些记录——UPDATEDELETESELECT ... FOR UPDATE ... 都没有。

  2. 现在您进行处理 - 无论这涉及到:合并、插入、更新其他表、写入日志条目...

  3. 作为处理的最后一步,您希望将记录“标记”为已处理。这究竟是如何实现的,并不重要。 可以在表中创建一个processed-列,并在处理完记录后将其设置为TRUE。或者可以有一个单独的表,其中包含已处理记录的主键(可能还有一个 load-job-id 来跟踪多个加载作业)。 无论以何种方式实现,this 都是需要捕获此processed 状态的时间点。

  4. COMMITROLLBACK(以防出现问题)。这将COMMIT 写入目标表的记录,处理状态信息,并且它将释放源表的排他锁。

如您所见,第 1 步 通过选择所有可以处理的所需记录(即它们没有被任何其他进程独占锁定)来解决可能丢失记录的问题。 第 3 步通过跟踪已处理的记录来处理可能被多次处理的记录问题。显然,必须在 Step 1 中检查此跟踪 - 这两个步骤是相互关联的,这就是我明确指出它们的原因。最后,所有的处理都发生在同一个 DB 事务上下文中,允许在整个事务中保证 COMMITROLLBACK。这意味着,在提交记录处理时,不会丢失任何“记录标记”。

现在,为什么这种方法比使记录“不可读”更可取? 因为系统中的其他进程。

也许源记录仍被事务系统读取但从未更新。此事务系统不必等待数据加载完成。

或者,也许有人想要对源数据进行一些分析,并且还需要读取这些记录。

或者您可能想要并行化数据加载:很容易跳过锁定的记录,只处理现在“可更新”的记录。参见例如Load balancing SQL reads while batch-processing?

好吧,我猜你希望有更容易消费的东西;唉,这就是我理解的这种要求的方法。

【讨论】:

  • 谢谢拉尔斯。这是一本很好的读物,并且非常接近我的方法。我会回来用一些代码更新问题。我正在努力解决的一件事是语法。
  • First off: there is no "read-only-once" in any RDBMS, including MS SQL 也很好奇 OP 如何在 MSSQL 上使用/实现只读一次 :)
  • @Suncatcher 我没有这样做。但是,我相信使用正确的技术是可能的。例如,使用 kafka 支持的 Confluent 平台。 confluent.io/blog/…
  • @Lars 我尝试搜索有关如何准确使用 FOR UPDATE 子句的文档,并发现此 SO 帖子的链接已损坏。你能指出我的语法吗?我发现 SAP HANA 参考文档中的示例很少。当然,我会离开并进行实验,但我认为文档需要改进以缩短我的实验工作,是吗?谢谢你。 stackoverflow.com/questions/26525489/…
  • 我在 ETL 工具(例如,IBM Data Stage 或 SAP BW 转换)中看到的另一种方法是实现读取分区:在表分区上,在整数列上通过取模数除以通过 num_readers、rowid 范围等,具体取决于表的内容。所以这是一个禁用两次读取记录的选项。然后,您可以将其标记为已处理或未处理,并根据状态在下一个批处理会话中考虑。
猜你喜欢
  • 2018-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-23
  • 1970-01-01
相关资源
最近更新 更多