dispatch_source_t

本文详细介绍了GCD中的DispatchSource,包括其基本概念、类型、创建及使用方法。通过实例展示了如何利用DispatchSource来监听定时器事件和自定义事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


  • Dispatch Source是GCD中的一个基本类型,从字面意思可称为调度源,它的作用是当有一些特定的较底层的系统事件发生时,调度源会捕捉到这些事件,然后可以做其他的逻辑处理,调度源有多种类型,分别监听对应类型的系统事件。我们来看看它都有哪些类型:
  • Timer Dispatch Source:定时调度源。
  • Signal Dispatch Source:监听UNIX信号调度源,比如监听代表挂起指令的SIGSTOP信号。
  • Descriptor Dispatch Source:监听文件相关操作和Socket相关操作的调度源。
  • Process Dispatch Source:监听进程相关状态的调度源。
  • Mach port Dispatch Source:监听Mach相关事件的调度源。
  • Custom Dispatch Source:监听自定义事件的调度源。

这一节就来看看如何使用Dispatch Source。

用通俗一点的话说就是用GCD的函数指定一个希望监听的系统事件类型,再指定一个捕获到事件后进行逻辑处理的闭包或者函数作为回调函数,然后再指定一个该回调函数执行的Dispatch Queue即可,当监听到指定的系统事件发生时会调用回调函数,将该回调函数作为一个任务放入指定的队列中执行。也就是说当监听到系统事件后就会触发一个任务,并自动将其加入队列执行,这里与之前手动添加任务的模式不同,一旦将Diaptach Source与Dispatch Queue关联后,只要监听到系统事件,Dispatch Source就会自动将任务(回调函数)添加到关联的队列中。

有些时候回调函数执行的时间较长,在这段时间内Dispatch Source又监听到多个系统事件,理论上就会形成事件积压,但好在Dispatch Source有很好的机制解决这个问题,当有多个事件积压时会根据事件类型,将它们进行关联和结合,形成一个新的事件。

监听事件类型

Dispatch Source一共可以监听六类事件,分为11个类型,我们来看看都是什么:

  • DISPATCH_SOURCE_TYPE_DATA_ADD:属于自定义事件,可以通过dispatch_source_get_data函数获取事件变量数据,在我们自定义的方法中可以调用dispatch_source_merge_data函数向Dispatch Source设置数据,下文中会有详细的演示。
  • DISPATCH_SOURCE_TYPE_DATA_OR:属于自定义事件,用法同上面的类型一样。
  • DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口发送事件。
  • DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。
  • DISPATCH_SOURCE_TYPE_PROC:与进程相关的事件。
  • DISPATCH_SOURCE_TYPE_READ:读文件事件。
  • DISPATCH_SOURCE_TYPE_WRITE:写文件事件。
  • DISPATCH_SOURCE_TYPE_VNODE:文件属性更改事件。
  • DISPATCH_SOURCE_TYPE_SIGNAL:接收信号事件。
  • DISPATCH_SOURCE_TYPE_TIMER:定时器事件。
  • DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:内存压力事件。

创建Dispatch Source

我们可以使用dispatch_source_create函数创建Dispatch Source,该函数有四个参数:

  • type:第一个参数用于标识Dispatch Source要监听的事件类型,共有11个类型。
  • handle:第二个参数是取决于要监听的事件类型,比如如果是监听Mach端口相关的事件,那么该参数就是mach_port_t类型的Mach端口号,如果是监听事件变量数据类型的事件那么该参数就不需要,设置为0就可以了。
  • mask:第三个参数同样取决于要监听的事件类型,比如如果是监听文件属性更改的事件,那么该参数就标识文件的哪个属性,比如DISPATCH_VNODE_RENAME
  • queue:第四个参数设置回调函数所在的队列。
let dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)        let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatchQueue)

上面的代码就是创建Dispatch Source的简单示例。

设置事件处理器

前文中提到过,当Dispatch Source监听到事件时会调用指定的回调函数或闭包,该回调函数或闭包就是Dispatch Source的事件处理器。我们可以使用dispatch_source_set_event_handlerdispatch_source_set_event_handler_f函数给创建好的Dispatch Source设置处理器,前者是设置闭包形式的处理器,后者是设置函数形式的处理器:

dispatch_source_set_event_handler(dispatchSource, {                print("Dispatch Source 事件处理器...")})// 根据闭包尾随的特性,还可以有下面的写法dispatch_source_set_event_handler(dispatchSource) {          print("Dispatch Source 事件处理器...")      }

从上面示例代码中可以看到,该函数有两个参数,第一个是设置目标Dispatch Source,第二个参数就是设置处理器了。

既然是事件处理器,那么肯定需要获取一些Dispatch Source的信息,GCD提供了三个在处理器中获取Dispatch Source相关信息的函数,比如handlemask。而且针对不同类型的Dispatch Source,这三个函数返回数据的值和类型都会不一样,下面来看看这三个函数:

  • dispatch_source_get_handle:这个函数用于获取在创建Dispatch Source时设置的第二个参数handle
    • 如果是读写文件的Dispatch Source,返回的就是描述符。
    • 如果是信号类型的Dispatch Source,返回的是int类型的信号数。
    • 如果是进程类型的Dispatch Source,返回的是pid_t类型的进程id。
    • 如果是Mach端口类型的Dispatch Source,返回的是mach_port_t类型的Mach端口。
  • dispatch_source_get_data:该函数用于获取Dispatch Source监听到事件的相关数据。 
    • 如果是读文件类型的Dispatch Source,返回的是读到文件内容的字节数。
    • 如果是写文件类型的Dispatch Source,返回的是文件是否可写的标识符,正数表示可写,负数表示不可写。
    • 如果是监听文件属性更改类型的Dispatch Source,返回的是监听到的有更改的文件属性,用常量表示,比如DISPATCH_VNODE_RENAME等。
    • 如果是进程类型的Dispatch Source,返回监听到的进程状态,用常量表示,比如DISPATCH_PROC_EXIT等。
    • 如果是Mach端口类型的Dispatch Source,返回Mach端口的状态,用常量表示,比如DISPATCH_MACH_SEND_DEAD等。
    • 如果是自定义事件类型的Dispatch Source,返回使用dispatch_source_merge_data函数设置的数据。
  • dispatch_source_get_mask:该函数用于获取在创建Dispatch Source时设置的第三个参数mask。在进程类型,文件属性更改类型,Mach端口类型的Dispatch Source下该函数返回的结果与dispatch_source_get_data一样。

注册Cancellation Handler

Cancellation Handler就是当Dispatch Source被释放时用来处理一些后续事情,比如关闭文件描述符或者释放Mach端口等。我们可以使用dispatch_source_set_cancel_handler函数或者dispatch_source_set_cancel_handler_f函数给Dispatch Source注册Cancellation Handler:

dispatch_source_set_cancel_handler(dispatchSource) {                print("进行善后处理...")      }

该函数有两个参数,第一个参数是目标Dispatch Source,第二个参数就是要进行善后处理的闭包或者函数。

更改Dispatch Source的目标队列

在上文中,我们说过可以使用dispatch_source_create函数创建Dispatch Source,并且在创建时会指定回调函数执行的队列,那么如果事后想更改队列,比如说想更改队列的优先级,这时我们可以使用dispatch_set_target_queue函数实现:

let dispatchQueueDefaultPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)        let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatchQueueDefaultPriority)   let dispatchQueueLowPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)     dispatch_set_target_queue(dispatchSource, dispatchQueueLowPriority)

这里需要注意的是,如果在更改目标队列时,Dispatch Source已经监听到相关事件,并且回调函数已经在之前的队列中执行了,那么会一直在旧的队列中执行完成,不会转移到新的队列中去。

暂停恢复Dispatch Source

暂停和恢复Dispatch Source与Dispatch Queue一样,都适用dispatch_suspenddispatch_resume函数。这里需要注意的是刚创建好的Dispatch Source是处于暂停状态的,所以使用时需要用dispatch_resume函数将其启动。

废除Dispatch Source

如果我们不再需要使用某个Dispatch Source时,可以使用dispatch_source_cancel函数废除,该函数只有一个参数,那就是目标Dispatch Source。

Dispatch Source实践

说了这么多,这一节来看看Dispatch Source到底怎么用。

用Dispatch Source监听定时器

Dispatch Source能监听的事件中有一个类型就是定时器,我们来看看如何实现:

class TestDispatchSource {      func launch() {          let dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)        let timer = createTimerDispatchSource(dispatch_time(DISPATCH_TIME_NOW, 0), interval: NSEC_PER_SEC * 5, leeway: 0, queue: dispatchQueue) {            print("处理定时任务,该任务每5秒执行一次...")        }        dispatch_resume(timer)        sleep(30)    }    func createTimerDispatchSource(startTime: dispatch_time_t, interval: UInt64, leeway: UInt64, queue: dispatch_queue_t, handler: dispatch_block_t) -> dispatch_source_t {        let timerDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)        dispatch_source_set_timer(timerDispatchSource, startTime, interval, leeway)        dispatch_source_set_event_handler(timerDispatchSource, handler)        return timerDispatchSource    }}

上面的代码示例中一个新的函数dispatch_source_set_timer,该函数的作用就是给监听事件类型为DISPATCH_SOURCE_TYPE_TIMER的Dispatch Source设置相关属性,该函数有四个参数:

  • source:该参数为目标Dispatch Source,类型为dispatch_source_t.
  • start:该参数为定时器的起始时间,类型为dispatch_time_t
  • interval:该参数为定时器的间隔时间,类型为UInt64,间隔时间的单位是纳秒。
  • leeway:该参数为间隔时间的精度,类型为UInt64,时间单位也是纳秒。
用Dispatch Source监听自定义事件

Dispatch Source能监听的事件中有一个类型是自定义事件,下面我们来看看如何使用:

class TestDispatchSource {        func launch() {        var totalProcess = 0        let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue())        dispatch_source_set_event_handler(dispatchSource) {            let process = dispatch_source_get_data(dispatchSource)            totalProcess += Int(process)            print("这里可以在主线程更新UI,显示进度条...进度为/(totalProcess)%")        }        dispatch_resume(dispatchSource)        generateCustomEvent(dispatchSource)    }    func generateCustomEvent(dispatchSource: dispatch_source_t) {        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)        for index in 0...100 {             dispatch_sync(queue) {                   print("模拟自定义事件...进度为/(index)%")                  dispatch_source_merge_data(dispatchSource, 1)                sleep(2)              }        }    }}

我们来看看generateCustomEvent(dispatchSource: dispatch_source_t)方法,该方法的作用的是模拟自定义事件,首先创建一个全局并发队列,然后循环让其执行任务,在执行的任务里调用dispatch_source_merge_data函数,就可以触发监听类型为DISPATCH_SOURCE_TYPE_DATA_ADD或者DISPATCH_SOURCE_TYPE_DATA_OR的Dispatch Source。该函数有两个参数,第一个参数是目标Dispatch Source,第二个参数的类型是无符号长整型,用于向目标Dispatch Source中的对应变量追加指定的数。

我们再来看看如何监听自定义时间,首先创建类型为DISPATCH_SOURCE_TYPE_DATA_ADD的Dispatch Source,然后设置回调闭包,在闭包中使用dispatch_source_get_data获取追加的变量值,该函数只有一个参数,就是目标Dispatch Source,这里需要注意的是通过dispatch_source_get_data函数获取的变量值并不是累加值,而是每次调用dispatch_source_merge_data函数时设置的值,所以在上面的示例中用totalProcess变量累加每次获取到的值。

上面的示例可以用来模拟后台进行下载,根据下载的数据量使用dispatch_source_merge_data函数给目标Dispatch Source设置相应的变量值,然后在主线程中监听到Dispatch Source的自定义事件,通过dispatch_source_get_data函数获取到变量,用于更新显示进度条的UI。



原文地址:http://www.th7.cn/Program/IOS/201605/849625.shtml

CREATE MATERIALIZED TABLE catalog_jp.dwd.tb_agrt_policy_curr_dim_materialized3 ( policy_id VARCHAR(2147483647), source_policy_id VARCHAR(2147483647), main_policy_owner_id VARCHAR(2147483647), source_main_policy_owner_id VARCHAR(2147483647), unified_main_policy_owner_id VARCHAR(2147483647), joint_policy_owner_id VARCHAR(2147483647), source_joint_policy_owner_id VARCHAR(2147483647), unified_joint_policy_owner_id VARCHAR(2147483647), channel_id VARCHAR(2147483647), source_channel_id VARCHAR(2147483647), servicing_agent_id VARCHAR(2147483647), source_servicing_agent_id VARCHAR(2147483647), agency_code VARCHAR(2147483647), agency_registration_number VARCHAR(2147483647), bank_agent_id VARCHAR(2147483647), source_bank_agent_id VARCHAR(2147483647), company_code VARCHAR(2147483647), dispatch_address_country_code VARCHAR(2147483647), dispatch_address_province_code VARCHAR(2147483647), dispatch_address_postcode VARCHAR(2147483647), dispatch_address_line_1 VARCHAR(2147483647), dispatch_address_line_2 VARCHAR(2147483647), dispatch_address_line_3 VARCHAR(2147483647), dispatch_address_line_4 VARCHAR(2147483647), dispatch_address_line_5 VARCHAR(2147483647), policy_type_code VARCHAR(2147483647), policy_status_code VARCHAR(2147483647), policy_sub_status_code VARCHAR(2147483647), policy_status_date DATE, submit_date DATE, issue_date DATE, policy_effective_date DATE, policy_end_date DATE, inforce_date DATE, lapse_date DATE, reinstatement_date DATE, surrender_date DATE, termination_date DATE, premium_status VARCHAR(2147483647), premium_status_date DATE, premium_start_date DATE, premium_end_date DATE, bill_to_date DATE, paid_to_date DATE, sum_assured DECIMAL(24, 4), modal_premium DECIMAL(24, 4), currency_code VARCHAR(2147483647), billing_method_code VARCHAR(2147483647), billing_frequency_code VARCHAR(2147483647), billing_type VARCHAR(2147483647), last_billing_date DATE, payor_id VARCHAR(2147483647), source_payor_id VARCHAR(2147483647), unified_payor_id VARCHAR(2147483647), standard_indicator VARCHAR(2147483647), next_premium_amt DECIMAL(24, 4), surrender_value DECIMAL(24, 4), outstanding_balance DECIMAL(24, 4), suspense_amt DECIMAL(24, 4), first_issue_date DATE, pledge_flag VARCHAR(2147483647), bonus_premium DECIMAL(24, 4), base_assured DECIMAL(24, 4), final_assured DECIMAL(24, 4), special_contract_type_code VARCHAR(2147483647), special_contract_type_name VARCHAR(2147483647), spouse_death_flag VARCHAR(2147483647), living_needs_rider_indicator VARCHAR(2147483647), specified_agent_claimant_rider_indicator VARCHAR(2147483647), automated_loan_indicator VARCHAR(2147483647), liability_start_date_specified_indicator VARCHAR(2147483647), joint_solicitation_flag VARCHAR(2147483647), corperate_policy_indicator VARCHAR(2147483647), ape DECIMAL(24, 4), grace_period_due_date DATE, guarantee_period DECIMAL(5, 0), guarantee_payment_amt DECIMAL(24, 4), reserve_amt DECIMAL(24, 4), saving_reserve_amt DECIMAL(24, 4), general_reserve_amt DECIMAL(24, 4), reserve_interest_rate DECIMAL(24, 4), reserve_amt_calculation_date DATE, death_benefit_amt DECIMAL(24, 4), investment_perf_linked_indicator VARCHAR(2147483647), fixed_life_transfer_indicator VARCHAR(2147483647), annuity_payment_start_date DATE, etl_valid_start_time TIMESTAMP(6), etl_valid_end_time TIMESTAMP(6), etl_is_current_flag VARCHAR(2147483647), etl_create_time TIMESTAMP(6), etl_create_by VARCHAR(2147483647), etl_update_time TIMESTAMP(6), etl_update_by VARCHAR(2147483647), etl_source_system_record_time TIMESTAMP(6), etl_source_bu VARCHAR(2147483647), etl_source_system VARCHAR(2147483647), etl_source_table VARCHAR(2147483647), data_arrival_time TIMESTAMP(6), dwd_data_arrival_time TIMESTAMP(6), PRIMARY KEY (policy_id) NOT ENFORCED ) WITH( 'connector' = 'paimon' ) FRESHNESS = INTERVAL '10' MINUTE REFRESH_MODE = CONTINUOUS AS有问题吗
最新发布
08-01
<think>我们正在处理一个Flink SQL创建物化表时出现的主键问题。用户遇到的错误是:`Could not create a PRIMARY KEY with nullable column 'policy_id'`。根据错误信息,问题在于主键列`policy_id`被定义为可空(nullable),而Flink要求主键列必须是非空的。 参考引用[1]中关于Flink SQL建表语句的说明,在定义列时,如果没有显式指定`NOT NULL`,则列默认可空。而主键约束要求所有主键列都必须是非空的。 另外,引用[3]中提供了两种使用Paimon连接器创建表的方式,并且在两种方式中,主键列都显式声明了`PRIMARY KEY (id) NOT ENFORCED`,但注意,这里并没有直接说明列是否为`NOT NULL`。然而,根据错误信息和Flink的要求,我们必须确保主键列在物理上是非空的。 因此,解决方案是:在创建表时,为主键列(这里是`policy_id`)显式添加`NOT NULL`约束。 下面我们给出一个修正后的SQL示例: ```sql CREATE MATERIALIZED TABLE my_paimon_table ( policy_id BIGINT NOT NULL, -- 显式声明为NOT NULL -- 其他列定义... PRIMARY KEY (policy_id) NOT ENFORCED ) WITH ( 'connector' = 'paimon', 'path' = '...', -- 指定Paimon表的存储路径 -- 其他Paimon相关的配置参数... ); ``` 注意: 1. 主键列`policy_id`后面必须加上`NOT NULL`。 2. 主键定义使用`PRIMARY KEY (policy_id) NOT ENFORCED`,其中`NOT ENFORCED`表示由用户保证主键约束(Flink本身不强制检查唯一性,但存储系统如Paimon会依赖这个主键进行更新删除等操作)。 另外,根据引用[3]中的说明,创建Paimon表有两种方式: - 通过Catalog创建(创建非临时表) - 通过WITH参数创建临时表 用户需要根据实际情况选择合适的方式。如果是永久表,建议使用Catalog方式;如果是临时表,可以使用WITH参数方式。 如果用户是在创建物化视图(Materialized Table)时使用`CREATE MATERIALIZED TABLE`语句,那么同样需要遵循上述规则。 此外,还需要注意,在物化表的查询语句中,必须确保`policy_id`不会出现NULL值。如果源数据中可能有NULL,需要使用`COALESCE`等函数处理。 例如: ```sql CREATE MATERIALIZED TABLE my_paimon_table WITH (...) AS SELECT COALESCE(policy_id, 0) AS policy_id, -- 如果可能为NULL,则用0代替 ... FROM ... ``` 如果用户按照上述方法修改后仍然遇到问题,可能需要检查: 1. 使用的Flink版本是否支持`CREATE MATERIALIZED TABLE`语法(注意:Materialized Table是Flink 1.16及以上版本引入的)。 2. Paimon连接器的版本是否兼容当前Flink版本。 3. 确保Paimon连接器的相关配置正确,特别是`path`参数指向正确的路径。 最后,关于引用[2]中的错误(Table sink doesn't support consuming update changes),这个错误通常是由于sink表不支持接收更新(update)或删除(delete)消息。而Paimon作为支持更新的存储系统,应该能够处理这些变更。因此,如果用户使用的是Paimon连接器,应该不会出现这个错误。但如果用户使用的是其他连接器(如CSV),则可能因为不支持更新操作而报错。这里用户使用的是Paimon,所以可以忽略引用[2]的问题。 总结:用户需要确保主键列在表定义中显式声明为`NOT NULL`,并在查询中保证主键列非空。</think>### Flink SQL 创建带主键的 Paimon 物化表解决方案 #### 错误原因分析 根据错误信息 `Could not create a PRIMARY KEY with nullable column 'policy_id'`,问题核心在于 **主键列 `policy_id` 被识别为可空字段**,违反 Flink SQL 主键约束的基本要求[^1]: ```bash org.apache.flink.table.api.ValidationException: Could not create a PRIMARY KEY with nullable column 'policy_id' A PRIMARY KEY column must be declared on non-nullable physical columns. ``` #### 修正后的 SQL 语法(Paimon 连接器) 以下是正确创建带主键的 Paimon 物化表语法: ```sql -- 方式1:通过 Catalog 创建永久表(推荐生产环境) CREATE CATALOG my_paimon WITH ( 'type' = 'paimon', 'warehouse' = 'hdfs:///path/to/warehouse' -- 替换为实际存储路径 ); USE CATALOG my_paimon; CREATE TABLE policy_table ( policy_id BIGINT NOT NULL, -- 关键:显式声明 NOT NULL policy_name STRING, effective_date DATE, PRIMARY KEY (policy_id) NOT ENFORCED -- Paimon 要求 NOT ENFORCED ) WITH ( 'bucket' = '4', 'bucket-key' = 'policy_id' ); -- 方式2:通过 WITH 参数创建临时表(测试环境) CREATE TEMPORARY TABLE temp_policy_table ( policy_id BIGINT NOT NULL, -- 必须 NOT NULL policy_type STRING, PRIMARY KEY (policy_id) NOT ENFORCED ) WITH ( 'connector' = 'paimon', 'path' = 'file:///tmp/paimon_data', 'auto-create' = 'true' -- 自动创建表结构 ); ``` #### 关键修复点说明 1. **主键列显式声明 `NOT NULL`** Flink 默认所有列可为空,必须手动添加 `NOT NULL` 约束[^1]: ```sql policy_id BIGINT NOT NULL -- 正确 -- policy_id BIGINT -- 错误(默认 nullable) ``` 2. **Paimon 连接器特殊要求** - 必须使用 `PRIMARY KEY (...) NOT ENFORCED` 语法[^3] - 建议配置 `bucket-key` 优化主键查询性能 ```sql PRIMARY KEY (policy_id) NOT ENFORCED -- 正确 -- PRIMARY KEY (policy_id) -- 错误(缺少 NOT ENFORCED) ``` 3. **物化视图数据源保证** 在 `AS SELECT` 查询中确保主键列非空: ```sql CREATE MATERIALIZED VIEW policy_mv AS SELECT COALESCE(policy_id, -1) AS policy_id, -- 空值处理 COUNT(*) AS claim_count FROM insurance_claims GROUP BY policy_id; ``` #### 配置验证矩阵 | 配置项 | 正确值 | 错误值 | 作用 | |--------|--------|--------|------| | **主键约束** | `NOT NULL` | 未声明 | 物理存储要求 | | **主键声明** | `NOT ENFORCED` | 缺省 | Paimon 连接器要求[^3] | | `auto-create` | `true` (新表) | `false` (已存表) | 自动初始化表结构 | | `bucket-key` | 主键列名 | 未配置 | 优化主键查询性能 | #### 常见错误场景处理 **场景1:源数据包含空主键** ```sql -- 添加数据清洗层 CREATE VIEW cleaned_policies AS SELECT CASE WHEN policy_id IS NULL THEN -ABS(RAND()) ELSE policy_id END AS policy_id, ... FROM raw_policies; ``` **场景2:多源 JOIN 主键冲突** ```sql CREATE TABLE merged_policies ( surrogate_key STRING NOT NULL PRIMARY KEY NOT ENFORCED ) WITH (...) AS SELECT CONCAT(src_system, '#', orig_id) AS surrogate_key -- 构建复合主键 FROM source1 UNION ALL FROM source2; ``` #### 验证步骤 1. 检查表元数据: ```sql DESCRIBE policy_table; -- 输出应显示 policy_id NOT NULL ``` 2. 测试空值写入: ```sql INSERT INTO policy_table VALUES (NULL, 'Test', DATE '2023-01-01'); -- 预期报错:Null value for primary key column ``` #### 相关问题 1. Flink SQL 中 `PRIMARY KEY NOT ENFORCED` 具体含义是什么?与数据库主键有何区别?[^3][^4] 2. 如何在 Paimon 表上配置自动合并小文件(compaction)? 3. 当主键包含多个列时,Paimon 的 `bucket-key` 应该如何配置? 4. Flink 物化视图(Materialized View)与 Paimon 表的关系是什么? 5. 如何监控 Paimon 表的主键冲突? 6. 在流式写入场景下,Paimon 如何处理主键更新(UPSERT)?[^2] 7. 为什么 Paimon 要求主键声明必须包含 `NOT ENFORCED`?这与传统数据库有何不同?[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值