《Delta Lake Up & Running》第四章:表格删除、更新和合并

由于Delta Lake为经典数据湖添加了事务层,因此我们可以执行经典的DML操作,例如更新、删除和合并。当在Delta表上执行删除操作时,操作是在数据文件级别执行的,根据需要删除和添加数据文件。已删除的数据文件不再是Delta表的当前版本的一部分,但不应立即物理删除,因为您可能希望通过时间旅行(时间旅行在第6章中讨论)恢复到表的旧版本。当运行更新操作时也是如此。根据需要,数据文件将从Delta表中添加和删除。

Delta Lake中最强大的DML操作是合并操作,它允许您在Delta表上执行"upsert"操作,即在Delta表上执行UPDATE、DELETE和INSERT操作的混合操作。您将源表和目标表连接在一起,编写匹配条件,然后指定匹配或不匹配的记录应该发生什么。

从Delta表中删除数据

我们将从一个干净的taxidb.YellowTaxis表开始。这个表是由第4.1章的"章节初始化"脚本创建的。它包含999,995万行:

sql 复制代码
%sql
SELECT  
    COUNT(id)
FROM    
    taxidb.YellowTaxis

输出:

sql 复制代码
+----------+
| count(1) |
+----------+
| 9999995  |
+----------+

表的创建和DESCRIBE HISTORY

taxidb.YellowTaxis Delta表是在"章节初始化"脚本中创建的,并复制到我们的/chapter04文件夹中。让我们查看该表的DESCRIBE HISTORY:

sql 复制代码
%sql
DESCRIBE HISTORY taxidb.YellowTaxis

输出(仅显示相关部分):

lua 复制代码
+-----------+--------------------------------+---------------------+
| operation | operationParameters            | operationMetrics    |
+-----------+--------------------------------+---------------------+
| WRITE     | [('mode', 'Overwrite'), (...)] | [('numFiles', '2'), |
|           |                                |  ('numOutputRows',  | 
|           |                                |  '9999995'), ...]   |
+-----------+--------------------------------+---------------------+

我们可以看到有一个事务,其中包含一个WRITE操作,写入了两个数据文件,总共包含9,999,995行。让我们查找有关这两个文件的一些详细信息。 在第2章中,您学习了如何使用事务日志来查看添加和删除文件的操作。让我们来看一下_delta_log目录:

bash 复制代码
%sh
ls /dbfs/mnt/datalake/book/chapter04/YellowTaxisDelta/_delta_log/*.json

正如预期的那样,我们只看到一个事务日志条目:

bash 复制代码
/dbfs/mnt/datalake/book/chapter04/YellowTaxisDelta/_delta_log/....0000.json

这个日志条目应该有两个文件添加操作,因为DESCRIBE HISTORY中的numfiles条目为两。再次使用我们的grep命令来查找这些部分:

bash 复制代码
%sh
grep \"add\" /dbfs/.../chapter04/YellowTaxisDelta/_delta_log/..0000.json |
  sed -n 1p > /tmp/commit.json
python -m json.tool < /tmp/commit.json

前一个命令的一个变种是,由于现在有两个条目,我们需要使用sed命令来提取正确的添加条目。

生成的输出(仅显示相关部分):

swift 复制代码
{
    "add": {
        "path": "part-00000-...-c000.snappy.parquet",
        ...
        "stats": "{\"numRecords\":5530100,...}}",
        "tags": {
            ...
        }
  }

在这里,我们看到了我们表格的第一个数据文件的名称,以及该文件中的记录数量。我们可以使用相同的命令sed -n 2p来获取第二个添加操作,以获取第二个数据文件:

bash 复制代码
{
    "add": {
        "path": "part-00001-...-c000.snappy.parquet",
        "...: ""
stats": {\"numRecords\":4469895,...}}",
        "tags": {
            ...
        }
    }
}

现在我们知道我们的表格有以下数据文件:

这些文件与我们的目录列表相对应,因此事务日志和目录列表报告是一致的:

bash 复制代码
%sh
ls -al /dbfs/mnt/datalake/book/chapter04/YellowTaxisDelta

drwxrwxrwx 2 _delta_log
-rwxrwxrwx 1 part-00000-d39cbaa1-ea7a-4913-a416-e229aa1d5616-c000.snappy.parquet
-rwxrwxrwx 1 part-00001-947cccf8-41ae-4212-a474-fedaa0f6623d-c000.snappy.parquet

执行DELETE操作

假设我们想要删除单个记录,这里是RideId = 100000的记录。首先,我们应该通过SQL SELECT确保该记录确实仍然存在于表中:

sql 复制代码
%sql
-- First, show that you have data for RideId = 10000
SELECT  
    RideId,
    VendorId,
    CabNumber,
    TotalAmount
FROM    
    taxidb.YellowTaxis
WHERE  
    RideId = 100000

输出:

diff 复制代码
+--------+----------+-----------+-------------+
| RideId | VendorId | CabNumber | TotalAmount |
+--------+----------+-----------+-------------+
| 100000 | 2        | T478827C  | 7.56        |
+--------+----------+-----------+-------------+

要删除这一行,我们可以使用简单的SQL DELETE。我们可以使用DELETE命令根据谓词或筛选条件有选择性地删除行:

sql 复制代码
%sql
DELETE FROM
    taxidb.YellowTaxis
WHERE RideId = 100000

输出:

diff 复制代码
+------------------+
|num_affected_rows |
+------------------+
|  1               |
+------------------+

我们可以确认我们实际上删除了一行。当我们使用DESCRIBE HISTORY命令查看表上的不同操作时,对于版本1,我们得到以下输出(为了可读性,行的输出进行了旋转):

sql 复制代码
version:             1
timestamp:           2022-12-14T17:50:23.000+0000
operation:           DELETE
operationParameters: [('predicate', 
                     '["(spark_catalog.taxidb.YellowTaxis.RideId = 100000)"]')]
operationMetrics:    [('numRemovedFiles', '1'),
                      ('numCopiedRows', '5530099'),
                      ('numAddedChangeFiles', '0'),
                      ('executionTimeMs', '32534'),
                      ('numDeletedRows', '1'),
                      ('scanTimeMs', '1524'),
                      ('numAddedFiles', '1'),
                      ('rewriteTimeMs', '31009')]

我们可以看到操作是DELETE,我们用于删除的谓词是WHERE RideId = 100000。Delta Lake删除了一个文件(numRemovedFiles = 1)并添加了一个新文件(numAddedFiles = 1)。如果我们使用我们信任的grep命令查找详细信息,情况如下:

Delta Lake在DELETE操作中执行以下操作:

  1. Delta Lake首先扫描数据,以识别包含与谓词条件匹配的行的文件。在这种情况下,文件是e229aa1d5616数据文件;它包含RideId = 100000的记录。
  2. 在第二次扫描中,Delta Lake将匹配的数据文件读入内存。此时,Delta Lake在将新的干净数据文件写入存储之前删除了相关的行。这个新的数据文件是0d3e0e77c4c1数据文件。由于Delta Lake删除了一条记录,这个新的数据文件包含5,530,099条记录(5,530,100 - 1)。
  3. 随着Delta Lake完成DELETE操作,数据文件e229aa1d5616现在从Delta事务日志中删除,因为它不再是Delta表的一部分。这个过程称为"墓碑标记"。然而,重要的是要注意,这个旧数据文件并没有被删除,因为您可能仍然需要它来回溯到表的早期版本。您可以使用VACUUM命令来删除早于某个时间段的文件。时间旅行和VACUUM命令在第6章中有详细介绍。
  4. 数据文件fedaa0f6623d仍然是Delta表的一部分,因为对它没有进行任何更改。

我们可以在目录列表中看到已添加到目录中的一个数据文件(0d3e0e77c4c1):

diff 复制代码
%sh
ls -al /dbfs/mnt/datalake/book/chapter04/YellowTaxisDelta/

drwxrwxrwx _delta_log
-rwxrwxrwx part-00000-96c2f047-99cc-4a43-b2ea-0d3e0e77c4c1-c000.snappy.parquet
-rwxrwxrwx part-00000-d39cbaa1-ea7a-4913-a416-e229aa1d5616-c000.snappy.parquet
-rwxrwxrwx part-00001-947cccf8-41ae-4212-a474-fedaa0f6623d-c000.snappy.parquet

数据文件e229aa1d5616并没有被物理删除。 从中最重要的信息是,删除事务发生在数据文件级别。Delta Lake将根据需要在事务日志中创建新的分区,并插入新的添加文件和删除文件操作。性能调整的第6章将涵盖VACUUM命令和其他清理不再需要的墓碑标记数据文件的方法。

DELETE性能调优提示

提升Delta Lake上DELETE操作性能的主要方法是添加更多谓词以缩小搜索范围。例如,如果您有分区化的数据,并且知道要删除的记录属于哪个分区,您可以将它们的分区子句添加到DELETE谓词中。

Delta Lake还提供了许多其他优化条件,如数据跳过和Z-order优化。Z-order重新组织每个数据文件的布局,使类似的列值被战略性地放置在一起,以实现最大效率。有关更多详细信息,请参考第5章。

在表中更新数据

现在您已经看到了DELETE操作对YellowTaxis表的影响,让我们快速看一下UPDATE操作。您可以使用UPDATE操作有选择地更新匹配筛选条件(也称为谓词)的任何行。

用例描述

让我们假设RideId = 9999994的记录的DropLocationId存在错误。首先,让我们使用以下SELECT确保此记录在我们的表中:

sql 复制代码
SELECT
    INPUT_FILE_NAME(),
    RideId,
    VendorId,
    DropLocationId
FROM    
    taxidb.YellowTaxis
WHERE
    RideId = 9999994

Spark SQL的INPUT_FILE_NAME()函数是一个方便的函数,它可以提供包含记录的文件名:

diff 复制代码
+---------------------------+---------+----------+----------------+
| input_file_name()         | RideId  | VendorId | DropLocationId |
+---------------------------+---------+----------+----------------+
| .../part-00001-...parquet | 9999994 | 1        | 243            |
+---------------------------+---------+----------+----------------+

INPUT_FILE_NAME函数显示我们的记录位于fedaa0f6623d数据文件中,这是有道理的,因为它是最后的记录,所以从逻辑上说它位于最后创建的数据文件中。我们可以看到现有的DropLocationId目前是243。假设我们要将此字段更新为值250。接下来,我们将查看实际的DELETE操作。

在表中更新数据

现在我们可以将UPDATE SQL语句编写如下:

ini 复制代码
%sql

UPDATE
    taxidb.YellowTaxis
SET
    DropLocationId = 250
WHERE
    RideId = 9999994

我们可以看到我们更新了一行数据:

diff 复制代码
+-------------------+
| num_affected_rows |
+-------------------+
|   1               |
+-------------------+

首先,让我们验证我们成功地更新了表格:

sql 复制代码
%sql
SELECT  
    RideId,
    DropLocationId
FROM
    taxidb.YellowTaxis
WHERE
    RideId = 9999994

+---------+----------------+
| RideId  | DropLocationId |
+---------+----------------+
| 9999994 | 250            |
+---------+----------------+

输出显示记录已成功更新。当我们在表格上使用DESCRIBE HISTORY命令时,我们可以看到表格版本3上的UPDATE操作(为了清晰起见,输出进行了旋转):

sql 复制代码
version:             3
timestamp:           2022-12-23 17:20:45+00:00
operation:           UPDATE
operationParameters: [('predicate', '(RideId = 9999994)')]
operationMetrics:    [('numRemovedFiles', '1'),
                      ('numCopiedRows', '4469894'),
                      ('numAddedChangeFiles', '0'),
                      ('executionTimeMs', '25426'),
                      ('scanTimeMs', '129'),
                      ('numAddedFiles', '1'),
                      ('numUpdatedRows', '1'),
                      ('rewriteTimeMs', '25293')]

有一个文件被移除('numRemovedFiles','1'),并且有一个文件被添加('numAddedFiles','1')。我们还可以看到我们的UPDATE谓词[('predicate','(RideId = 9999994)')]。如果我们使用grep命令查找细节,情况如下:

Delta Lake对表执行更新操作分为两个步骤:

  1. 它查找并选择包含与谓词匹配的数据的数据文件,因此需要更新这些数据。Delta Lake尽可能使用数据跳过以加速此过程。在这种情况下,数据文件为fedaa0f6623d。我们还可以使用INPUT_FILE_NAME() SQL函数来验证这一点。
  2. 接下来,Delta Lake将每个匹配的文件读入内存,更新相关行,并将结果写入新的数据文件。在这种情况下,新的数据文件是50807db851f6。它现在包含fedaa0f6623d分区的所有记录,但已应用了更新,本例中是RideId = 9999994的更新。这个数据文件是50807db851f6。这个数据文件继续保持4,469,895条记录。一旦Delta Lake成功执行了UPDATE操作,它会为新的数据文件添加一个添加文件操作。

由于不再需要,数据文件fedaa0f6623d会在事务日志中通过删除提交操作从Delta表中移除。然而,与DELETE操作一样,文件并没有被物理删除,以防我们需要查看表的旧版本。数据文件0d3e0e77c4c1不受我们的更新影响,因此它仍然是Delta表的一部分,并继续保持5,530,099条记录。

性能调优技巧更新

与DELETE操作类似,提升Delta Lake上UPDATE命令性能的主要方法是添加更多的谓词以缩小搜索范围。搜索条件越具体,Delta Lake需要扫描和/或修改的文件就越少。 正如在前一节中提到的,Delta Lake的其他特性,如Z-ordering,可以用来进一步加速UPDATE操作。有关Delta Lake优化的详细信息,请参阅第5章。

使用MERGE操作进行数据Upsert

Delta Lake的MERGE命令允许您执行数据Upsert操作。Upsert是UPDATE和INSERT命令的结合。为了理解upserts,让我们假设我们有一个现有表(目标表)和一个包含新记录和对现有记录的更新的源表。以下是upsert的实际工作方式:

  • 当源表中的记录与目标表中的现有记录匹配时,Delta Lake会更新该记录。
  • 当没有这样的匹配时,Delta Lake会插入新记录。

用例描述

让我们将MERGE操作应用到我们的YellowTaxis表上。让我们对YellowTaxis表进行计数:

sql 复制代码
%sql
SELECT
    COUNT(*)
FROM
    taxidb.YellowTaxis

我们可以看到我们有9,999,994条记录。

sql 复制代码
+----------+
| count(1) |
+----------+
| 9999994  |
+----------+

我们希望重新插入在本章的DELETE部分中删除的RideId为100000的记录。因此,在我们的源数据中,我们需要一条RideId设置为100000的记录。 对于这个示例,让我们假设我们还希望更新RideId为999991的记录,因为VendorId被错误地插入,需要将它们的VendorId更新为1(VendorId = 1)这五条记录。

最后,我们希望将记录数调整为恰好10,000,000条记录,因此我们需要再增加5条记录,RideId范围从999996到10000000。

合并数据集

在我们为本书提供的伴随源数据文件中,有一个名为YellowTaxisMergeData.csv的文件,其中包含了这些记录。由于我们需要提供模式(schema),我们首先从现有表中读取模式:

ini 复制代码
df = spark.read.format("delta").table("taxidb.YellowTaxis")
yellowTaxiSchema = df.schema
print(yellowTaxiSchema)

一旦我们加载了模式,就可以加载我们的合并数据CSV文件:

bash 复制代码
yellowTaxisMergeDataFrame = spark      \
            .read                      \
            .option("header", "true")  \
            .schema(yellowTaxiSchema)  \
            .csv("/mnt/datalake/book/chapter04/YellowTaxisMergeData.csv") 
            .sort(col("RideId"))

yellowTaxisMergeDataFrame.show()

这里显示了部分输出:

yaml 复制代码
+----------+----------+------------------------------+
| RideId   | VendorId | PickupTime                   |
+----------+----------+------------------------------+
|   100000 | 2        | 2022-03-01T00:00:00.000+0000 |
|  9999991 | 1        | 2022-04-04T20:54:04.000+0000 |
|  9999992 | 1        | 2022-04-04T20:54:04.000+0000 |
|  9999993 | 1        | 2022-04-04T20:54:04.000+0000 |
|  9999994 | 1        | 2022-04-04T20:54:04.000+0000 |
|  9999995 | 1        | 2022-04-04T20:54:04.000+0000 |
|  9999996 | 3        | 2022-03-01T00:00:00.000+0000 |
|  9999997 | 3        | 2022-03-01T00:00:00.000+0000 |
|  9999998 | 3        | 2022-03-01T00:00:00.000+0000 |
|  9999999 | 3        | 2022-03-01T00:00:00.000+0000 |
| 10000000 | 3        | 2022-03-01T00:00:00.000+0000 |
+----------+----------+------------------------------+

我们可以看到我们的RideId为100000的记录,五条新的记录(从9999991到9999995)的VendorId已经更新为1,以及从9999996开始的五条新记录。 我们希望在SQL中编写我们的MERGE语句,因此我们需要在SQL中使用我们的DataFrame。DataFrame类有一个非常方便的方法叫做createOrReplaceTempView,它正是这个目的:

bash 复制代码
# Create a Temporary View on top of our DataFrame, making it
# accessible to the SQL MERGE statement below
yellowTaxisMergeDataFrame.createOrReplaceTempView("YellowTaxiMergeData")

现在我们可以在SQL中使用视图名称:

sql 复制代码
%sql
SELECT
    *
FROM    
    YellowTaxiMergeData

这显示的结果与使用DataFrame的display()方法完全相同。

MERGE语句

现在你可以按照以下方式编写你的MERGE语句:

sql 复制代码
%sql
MERGE INTO taxidb.YellowTaxis AS target
    USING YellowTaxiMergeData AS source
        ON target.RideId = source.RideId

-- You need to update the VendorId if the records
-- matched
WHEN MATCHED                                      
    THEN
        -- If you want to update all columns,
        -- you can say "SET *"
        UPDATE SET target.VendorId = source.VendorId
WHEN NOT MATCHED
    THEN
        -- If all columns match, you can also do a "INSERT *"
        INSERT(RideId, VendorId, PickupTime, DropTime, PickupLocationId,
               DropLocationId, CabNumber, DriverLicenseNumber, PassengerCount,
               TripDistance, RateCodeId, PaymentType, TotalAmount, FareAmount,
               Extra, MtaTax, TipAmount, TollsAmount, ImprovementSurCharge)
        VALUES(RideId, VendorId, PickupTime, DropTime, PickupLocationId,
               DropLocationId, CabNumber, DriverLicenseNumber, PassengerCount,
               TripDistance, RateCodeId, PaymentType, TotalAmount, FareAmount,
               Extra, MtaTax, TipAmount, TollsAmount, ImprovementSurCharge)

让我们分析这个语句:

  • 我们将执行MERGE INTO操作,目标是YellowTaxis Delta表。请注意,我们为表起了一个别名source。
  • 使用USING子句,我们指定了源数据集,这里是视图YellowTaxiMergeData,并为其起了别名source。
  • 定义源数据集与目标数据集之间的连接条件。在我们的情况下,我们只想在VendorId上进行连接。如果你有分区数据,并且想要针对一个分区进行操作,你可能需要在这里使用AND语句添加条件。
  • 指定当RideId在源和目标之间匹配时的操作。在这个用例中,我们希望使用源的VendorId(设置为1)来更新源数据。在这里,我们只更新了一个列,但如果需要,可以提供一个由逗号分隔的列列表。如果要更新所有列,我们可以简单地使用UPDATE SET *。
  • 定义当记录存在于源但不存在于目标时的操作。在WHEN NOT MATCHED中,我们没有添加任何其他条件,但如果用例需要,可以添加任何其他子句。大多数情况下,您将提供一个INSERT语句作为操作。由于我们的源和目标列名相同,我们也可以使用简单的INSERT *。

当执行这个MERGE语句时,我们会得到以下输出:

diff 复制代码
+-------------------+------------------+------------------+-------------------+
| num_affected_rows | num_updated_rows | num_deleted_rows | num_inserted_rows |
+-------------------+------------------+------------------+-------------------+
|        11         |        5         |        0         |         6         |
+-------------------+------------------+------------------+-------------------+

这个输出正是您所期望的:

  • 我们更新了五行(VendorId从9999991到9999995)
  • 我们插入了六行:
  • 一行的RideId为100000
  • 最后五行(从9999996到10000000)

我们可以看到前五行的更新:

sql 复制代码
%sql
-- Make sure that the VendorId has been updated
-- for the records with RideId between
-- 9999991 and 9999995
SELECT
    RideId,
    VendorId
FROM
    taxidb.YellowTaxis
WHERE RideId BETWEEN 9999991 and 9999995
+---------+----------+
| RideId  | VendorId |
+---------+----------+
| 9999991 | 1        |
| 9999992 | 1        |
| 9999993 | 1        |
| 9999994 | 1        |
| 9999995 | 1        |
+---------+----------+

现在所有行的VendorId都已经变成了源VendorId 1。

我们可以看到插入的RideId为100000的记录:

sql 复制代码
%sql
--Make sure that you have a record with VendorId = 100000
SELECT
    *
FROM    
    taxidb.YellowTaxis
WHERE
    RideId = 100000

输出(显示部分输出):

diff 复制代码
+--------+----------+---------------------------+---------------------------+
| RideId | VendorId | PickupTime                | DropTime                  |
+--------+----------+---------------------------+---------------------------+
| 100000 | 2        | 2022-03-01 00:00:00+00:00 | 2022-03-01 00:12:01+00:00 |
+--------+----------+---------------------------+---------------------------+

最后,我们可以看到具有 RideId > 9999995 的新行:

sql 复制代码
%sql
SELECT  
    *
FROM
    taxidb.YellowTaxis
WHERE
    RideId > 9999995

+----------+----------+---------------------------+
| RideId   | VendorId | PickupTime                |
+----------+----------+---------------------------+
| 9999996  | 3        | 2022-03-01 00:00:00+00:00 |
| 9999997  | 3        | 2022-03-01 00:00:00+00:00 |
| 9999998  | 3        | 2022-03-01 00:00:00+00:00 |
| 9999999  | 3        | 2022-03-01 00:00:00+00:00 |
| 10000000 | 3        | 2022-03-01 00:00:00+00:00 |
+----------+----------+---------------------------+

总共有一千万条记录:

sql 复制代码
%sql
SELECT  
    COUNT(RideId)
FROM    
    taxidb.YellowTaxis

+----------+
| count(1) |
+----------+
| 10000000 |
+----------+

使用MERGE修改不匹配的行:

Delta Lake的MERGE操作中的一个重要补充是最近发布的"WHEN NOT MATCHED BY SOURCE"子句。该子句可用于更新或删除目标表中没有与源表对应的记录。这可以用于删除目标表中不再存在于源表中的记录,或用于标记表示数据已删除或不活动的记录,同时仍然保留这些记录在目标表中(即软删除)。

要删除源表中存在但目标表中不存在的记录(即硬删除),请使用"WHEN NOT MATCHED BY SOURCE"子句,如以下示例代码中所示:

sql 复制代码
%sql
MERGE INTO taxidb.YellowTaxis AS target
    USING YellowTaxiMergeData AS source
        ON target.RideId = source.RideId
WHEN MATCHED                                      
        UPDATE SET *
WHEN NOT MATCHED
        INSERT *
-- DELETE records in the target that are not matched by the source
WHEN NOT MATCHED BY SOURCE
    DELETE

如果您希望标记目标表中不再存在于源表中但满足特定条件的记录(即软删除),您可以指定一个MERGE条件和一个UPDATE操作:

sql 复制代码
%sql
MERGE INTO taxidb.YellowTaxis AS target
    USING YellowTaxiMergeData AS source
        ON target.RideId = source.RideId
WHEN MATCHED                                      
        UPDATE SET *
WHEN NOT MATCHED
        INSERT *
-- Set target.status = 'inactive' when records in the target table 
-- don't exist in the source table and condition is met
WHEN NOT MATCHED BY SOURCE target.PickupTime >= 
  (current_date() - INTERVAL '5' DAY) 
THEN
          UPDATE SET target.status = 'inactive'

最佳实践是在添加"WHEN NOT MATCHED BY SOURCE"子句以更新或删除目标行时,同时添加可选的MERGE条件。这是因为如果没有指定MERGE条件,可能会导致大量目标行被修改。因此,为了获得最佳性能,应用一个MERGE条件到"WHEN NOT MATCHED BY SOURCE"子句中(例如,在前面的示例代码中使用 target.PickupTime >= (current_date() - INTERVAL '5' DAY) 来限制要更新或删除的目标行数,因为只有当该条件对某行为真时,才会修改目标行。

您还可以将多个"WHEN NOT MATCHED BY SOURCE"子句添加到MERGE操作中。当存在多个子句时,它们按照指定的顺序进行评估,所有"WHEN NOT MATCHED BY SOURCE"子句,除了最后一个子句,都必须包含条件。

使用DESCRIBE HISTORY分析MERGE操作

当我们在YellowTaxis表上运行DESCRIBE HISTORY时,在输出的operationsParameters部分,我们可以看到我们的MERGE谓词:

rust 复制代码
operation: MERGE

[('predicate',
  '(target.RideId = source.RideId)'),
   ('matchedPredicates', '[{"actionType":"update"}]'),
   ('notMatchedPredicates', '[{"actionType":"insert"}]')]

我们可以看到连接条件(target.RideId = source.RideId),指定更新的matchedPredicate以及指定插入的notMatchedPredicate。operationMetrics输出部分显示了不同操作的详细信息:

css 复制代码
[('numTargetRowsCopied', '4469890'),  ('numTargetRowsDeleted', '0'),  ('numTargetFilesAdded', '4'),  ('executionTimeMs', '91902'),  ('numTargetRowsInserted', '6'),  ('scanTimeMs', '8452'),  ('numTargetRowsUpdated', '5'),  ('numOutputRows', '4469901'),  ('numTargetChangeFilesAdded', '0'),  ('numSourceRows', '11'),  ('numTargetFilesRemoved', '1'),  ('rewriteTimeMs', '16782')]

在这里,我们再次可以看到有六行被插入(numTargetRowsInserted),并且有五行被更新(numTargetRowsUpdated)。我们的Delta表中新增了四个新的数据文件,而一个数据文件被移除。

MERGE 操作的内部工作原理

在内部,Delta Lake 完成 MERGE 操作的步骤如下: 首先,它执行目标表和源表之间的内部连接,以选择包含匹配的所有数据文件。这可以防止不必要地传输数据,可以安全地忽略。 接下来,它在目标表和源表中选择的文件之间执行外部连接,并根据用户指定的 INSERT、DELETE 或 UPDATE 子句应用相应的操作。 MERGE 与 UPDATE 或 DELETE 在底层的主要不同之处在于 Delta Lake 使用连接来完成 MERGE。这允许您在尝试提高性能时使用一些独特的策略。

总结

像DELETE、UPDATE和MERGE这样的DML操作对于任何表格格式和ETL操作都是必不可少的操作,所有这些操作都是通过事务日志来实现的。通过利用这些操作,您可以开始高效处理数据变更并在数据平台中维护数据完整性。

与传统的关系数据库管理系统中的表格类似,您在本章中了解到,使用Delta表格,您可以执行DELETE、UPDATE和MERGE操作,但您还可以使用SQL或DataFrame API来应用这些操作。更重要的是,您了解了Delta Lake内部是如何处理Delta表格目录中的底层文件,以及事务日志如何记录和跟踪这些不同类型的条目。使用DESCRIBE HISTORY命令,我们可以查看有关表格事务输出的详细信息。在执行操作期间,每个操作也可以利用谓词来减少扫描的文件数量并提高性能。在操作期间使用谓词之外,还有其他性能调整技术可以应用于Delta表格,您将在接下来的章节中学习到。

相关推荐
不是笨小孩i2 小时前
Git常用指令
大数据·git·elasticsearch
canonical_entropy2 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
howard20052 小时前
大数据概念与价值
大数据·特征·概念·价值
知识分享小能手3 小时前
mysql学习教程,从入门到精通,SQL DISTINCT 子句 (16)
大数据·开发语言·sql·学习·mysql·数据分析·数据库开发
紫钺-高山仰止3 小时前
【脑机接口】脑机接口性能的电压波形的尖峰分类和阈值比较
大数据·分类·数据挖掘
Alluxio3 小时前
选择Alluxio来解决AI模型训练场景数据访问的五大理由
大数据·人工智能·分布式·ai·语言模型
沛沛老爹3 小时前
服务监控插件全览:提升微服务可观测性的利器
微服务·云原生·架构·datadog·influx·graphite
武子康4 小时前
大数据-133 - ClickHouse 基础概述 全面了解
java·大数据·分布式·clickhouse·flink·spark
huaqianzkh4 小时前
了解华为云容器引擎(Cloud Container Engine)
云原生·架构·华为云
Kika写代码5 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构