我过去为比较数据库之间的数据所做的是创建每列“类似 MD5”的签名。在您的情况下,您可以对“pre”表格内容和“post”表格内容执行类似的操作。这只会告诉您哪些列不同,但这可能就是您所需要的。
存在差异时进行调试可能很困难,但您可以将表的副本“保存”到 S3 以供调试使用。这可能会破坏您正在寻找的速度,并且您可能只想在出现问题或开启测试时以这种方式运行。您还可以按“日期”运行这样的过程,以便获得不匹配的日期和列。
由于非 Redshift 数据库并不总是像 Redshift 一样快,因此我以几种不同的方式制作了这样的签名。由于您将 Redshift 与 Redshift 进行比较,因此比较过程变得更容易和更快。在这种情况下,我要做的是为每一列执行 MD5(columnN::text),然后将 base64 结果的一部分转换为 BIGINT。然后你可以 sum() 每一列的这些值。 (SUM() 是聚合列信息并使用 MD5 结果子集的最简单方法。)由于 MD5 签名很大,使用结果子集很好,因为 MD5 散列将“唯一性”传播到结果中。溢出可能是一个问题,因此为每个值添加一个负常数可以帮助解决这个问题。生成的查询将类似于:
select
sum(nvl(strtol(substring({{column.column_name}}, 17, 8), 16) - (1::bigint << 31), 0))
from <<CTE>>;
这是来自我用于此过程的 jinja2 模板,它允许我读取表 DDL 并将非文本列转换为 CTE 中的文本。希望这个 sn-p 足够清楚该过程是如何工作的。
================= 更新================
我觉得 jinja2 的使用有些混乱。 Jinja2 是一种模板语言,可用于根据某些输入扩展文本。它不执行任何签名和比较表的 SQL 工作。这是一种更快地为不同的表重复这项工作的方法。
下面是为表创建签名的示例:
select
(
sum(nvl(strtol(substring(USAF, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(WBAN, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(STATION_NAME, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(CTRY, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(STATE, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(ICAO, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(LAT, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(LON, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(ELEV, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(begin_date, 17, 8), 16) - (1::bigint << 31), 0))
+ sum(nvl(strtol(substring(END_date, 17, 8), 16) - (1::bigint << 31), 0))
) as "hash"
from (
select
md5(USAF::text) as USAF
, md5(WBAN::text) as WBAN
, md5(STATION_NAME::text) as STATION_NAME
, md5(CTRY::text) as CTRY
, md5(STATE::text) as STATE
, md5(ICAO::text) as ICAO
, md5(LAT::text) as LAT
, md5(LON::text) as LON
, md5(ELEV::text) as ELEV
, md5(floor(extract(epoch from begin_date))::text) as begin_date
, md5(floor(extract(epoch from END_date))::text) as END_date
from public.gsod_stations
);
您可以看到,对于每一列,都需要计算一些哈希值(在本例中为 md5),并且如何完成取决于数据类型。然后将这些散列相加以创建列级散列,最后将这些列级散列相加以创建表级散列。 (1::bigint << 31 的偏移量用于帮助防止大表溢出。)这并不难理解,但是为每个表创建这个 SQL 是一件很痛苦的事情。
这就是 jinja2 的用武之地。通过模板中的 SQL 和模板中表的 DDL,jinja 可以生成 SQL。
Jinja SQL 模板(Redshift SQL 语法):
{% for table in tables %}
{%- if table.table_name == target or target is not defined %}
{% set vars = {"first_column":true} %}
select
(
{% for column in table.col_names -%}
{%- if not column.skip_compare -%}
{%- if vars.first_column -%}
{%- if vars.update({"first_column": false}) -%} {%- endif -%}
{%- else -%}
{% raw %}+ {% endraw %}
{%- endif -%}
sum(nvl(strtol(substring({{column.column_name}}, 17, 8), 16) - (1::bigint << 31), 0))
{%- else -%}
-- skipping {{column.column_name}}
{%- endif %}
{% endfor -%}
) as "hash"
from (
select
{%- set vars = {"first_column":true} %}
{% for column in table.col_names -%}
{%- if not column.skip_compare -%}
{%- if vars.first_column -%}
{%- if vars.update({"first_column": false}) -%} {%- endif -%}
{%- else -%}
{% raw %}, {% endraw %}
{%- endif -%}
{%- if column.RS_col_type in ["date", "timestamp"] -%}
md5(floor(extract(epoch from {{column.column_name}}))::text) as {{column.column_name}}
{%- elif column.RS_col_type in ["boolean", "bool"] -%}
md5(({{column.column_name}}::int)::text) as {{column.column_name}}
{%- else -%}
md5({{column.column_name}}::text) as {{column.column_name}}
{%- endif -%}
{%- else -%}
-- skipping {{column.column_name}}
{%- endif %}
{% endfor -%}
from {{table.RS_schema}}.{{table.table_name}}
)
;
{% endif %}
{% endfor %}
还有一个示例 json DDL 文件(包含 2 个表的信息):
{"tables":
[
{"table_name":"gsod_stations",
"RS_schema":"public",
"col_names": [
{"column_name":"USAF", "RS_col_type":"varchar(10)", "RS_col_params":"ENCODE zstd"},
{"column_name":"WBAN", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
{"column_name":"STATION_NAME", "RS_col_type":"varchar(80)", "RS_col_params":"ENCODE zstd"},
{"column_name":"CTRY", "RS_col_type":"varchar(30)", "RS_col_params":"ENCODE zstd"},
{"column_name":"STATE", "RS_col_type":"varchar(30)", "RS_col_params":"ENCODE zstd"},
{"column_name":"ICAO", "RS_col_type":"varchar(30)", "RS_col_params":"ENCODE zstd"},
{"column_name":"LAT", "RS_col_type":"float4", "RS_col_params":"ENCODE zstd"},
{"column_name":"LON", "RS_col_type":"float4", "RS_col_params":"ENCODE zstd"},
{"column_name":"ELEV", "RS_col_type":"float4", "RS_col_params":"ENCODE zstd"},
{"column_name":"begin_date", "RS_col_type":"date", "RS_col_params":"ENCODE zstd"},
{"column_name":"END_date", "RS_col_type":"date", "RS_col_params":"ENCODE zstd"}
],
"RS_sort_stmnt":"SORTKEY (USAF,WBAN)",
"RS_dist_stmnt":"DISTKEY (USAF)"},
{"table_name":"gsod_weather_station_data",
"RS_schema":"public",
"col_names": [
{"column_name":"station_wban", "RS_col_type":"varchar(12)", "RS_col_params":"ENCODE zstd"},
{"column_name":"station_id", "RS_col_type":"varchar(10)", "RS_col_params":"NOT NULL ENCODE zstd"},
{"column_name":"wban", "RS_col_type":"integer", "RS_col_params":"NOT NULL ENCODE zstd"},
{"column_name":"yearmoda", "RS_col_type":"date", "RS_col_params":"NOT NULL ENCODE raw"},
{"column_name":"temp", "RS_col_type":"decimal(8,2)", "RS_col_params":"NULL ENCODE zstd"},
{"column_name":"tcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
{"column_name":"dewpoint", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"dcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
{"column_name":"slp", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"slpcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
{"column_name":"stp", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"stpcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
{"column_name":"visibility", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"vcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
{"column_name":"wind_speed", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"wcount", "RS_col_type":"integer", "RS_col_params":"ENCODE zstd"},
{"column_name":"max_wind_speed", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"max_wind_gust", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"max_temp", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"maxtflags", "RS_col_type":"char(2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"min_temp", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"mintflags", "RS_col_type":"char(2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"precip", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"pflags", "RS_col_type":"char(2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"snow_depth", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"fog", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
{"column_name":"rain", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
{"column_name":"snow", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
{"column_name":"hail", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
{"column_name":"thunder", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
{"column_name":"tornado", "RS_col_type":"bool", "RS_col_params":"ENCODE zstd"},
{"column_name":"STATION_NAME", "RS_col_type":"varchar(80)", "RS_col_params":"ENCODE zstd"},
{"column_name":"CTRY", "RS_col_type":"char(30)", "RS_col_params":"ENCODE zstd"},
{"column_name":"STATE", "RS_col_type":"char(30)", "RS_col_params":"ENCODE zstd"},
{"column_name":"ICAO", "RS_col_type":"char(30)", "RS_col_params":"ENCODE zstd"},
{"column_name":"LAT", "RS_col_type":"decimal(8,3)", "RS_col_params":"ENCODE zstd"},
{"column_name":"LON", "RS_col_type":"decimal(8,3)", "RS_col_params":"ENCODE zstd"},
{"column_name":"ELEV", "RS_col_type":"decimal(8,2)", "RS_col_params":"ENCODE zstd"},
{"column_name":"begin_date", "RS_col_type":"date", "RS_col_params":"ENCODE zstd", "skip_compare":true},
{"column_name":"END_date", "RS_col_type":"date", "RS_col_params":"ENCODE zstd"}
],
"RS_sort_stmnt":"SORTKEY (yearmoda, station_wban)",
"RS_dist_stmnt":"DISTKEY (station_wban)"}
]
}
可以运行生成的 SQL 并为每个表创建签名。您希望在表之间以及可能在数据库之间比较这些签名。这正是我开发这个过程的原因。由于您要比较的表/数据库可能会有所不同,因此您将如何进行比较也会有所不同,但基本上是比较这些哈希值以查看这些表是否包含相同的内容。
注意事项:
- 虽然 Redshift 在生成 md5 值并将它们求和方面非常快,但对于大型数据集,其他数据库就没有那么快了。对于此类数据库,我不得不将哈希“简化”为更简单的东西。
- 任何散列都是有损的,并且有一些有限的可能性让 2 个事物散列相同,或者在这种情况下 2 个散列的总和错误匹配。这种可能性非常低,但不是零。
- Md5 散列非常独特,散列的每个部分也是唯一的,只是程度不同。通过选择 md5 的一部分作为总和,可以提高操作速度,同时保持非常高的置信度,即不存在错误比较。
- 此过程只能表示“匹配”或“不匹配”。要定位差异,需要对表的子集进行一些散列和/或直接比较表值。换句话说,如果(何时)您需要调试为什么事情不匹配,那么只有旧版本表的哈希签名对您没有帮助。建议将旧版本的表存档用于此类调试目的。