访问历史记录¶
本主题介绍了 Snowflake 中用户访问历史记录的概念。
本主题内容:
概述¶
Snowflake 中的访问历史记录是指用户查询读取数据的时间,以及 SQL 语句执行从源数据对象到目标数据对象的数据写入操作(如 INSERT、UPDATE、DELETE 以及 COPY 命令变体)的时间。查询 Account Usage ACCESS_HISTORY 视图可以找到用户访问历史记录。此视图中的记录有助于监管合规性审计,并提供有关常用和频繁访问的表和列的见解,因为用户(即查询操作员)、查询、表或视图、列和数据之间存在直接关联。
ACCESS_HISTORY 视图中的每一行均包含对应于每条 SQL 语句的一条记录。该记录包含以下类型的信息:
查询直接和间接访问的 源列,例如查询数据来自的基础表。
用户在查询结果中看到的 投影列,例如 SELECT 语句中指定的列。
用于确定查询结果但未投影的列,例如用于筛选结果的 WHERE 子句中的列。
例如:
CREATE OR REPLACE VIEW v1 (vc1, vc2) AS
SELECT c1 as vc1,
c2 as vc2
FROM t
WHERE t.c3 > 0
;
C1 和 C2 列是视图直接访问的源列,这些列记录在 ACCESS_HISTORY 视图的
base_objects_accessed
列中。C3 列用于筛选视图包含的行,这些行记录在 ACCESS_HISTORY 视图的
base_objects_accessed
列中。VC1 和 VC2 列是用户在查询视图时看到的投影列,
SELECT * FROM v1;
,这些列记录在 ACCESS_HISTORY 视图的direct_objects_accessed
列中。
同样的行为适用于 WHERE 子句中的键列。例如:
CREATE OR REPLACE VIEW join_v (vc1, vc2, c1) AS
SELECT
bt.c1 AS vc1,
bt.c2 AS vc2,
jt.c1
FROM bt, jt
WHERE bt.c3 = jt.c1;
创建视图需要两个不同的表:
bt
(基表)和jt
(联接表)。基表中的 C1、C2 和 C3 列以及联接表中的 C1 列都记录在 ACCESS_HISTORY 视图的
base_objects_accessed
列中。VC1、VC2 和 C1 列是用户在查询视图时看到的投影列,
SELECT * FROM join_v;
,这些列记录在 ACCESS_HISTORY 视图的direct_objects_accessed
列中。
备注
Account Usage QUERY_HISTORY 视图中的记录并非始终会记录在 ACCESS_HISTORY 视图中。SQL 语句的结构决定 Snowflake 是否在 ACCESS_HISTORY 视图中记录一个条目。
有关 ACCESS_HISTORY 视图中 Snowflake 支持的读取和写入操作的详细信息,请参阅视图的 使用说明。
跟踪读取和写入操作¶
ACCESS_HISTORY 视图包含以下列:
query_id | query_start_time | user_name | direct_objects_accessed | base_objects_accessed | objects_modified | object_modified_by_ddl | policies_referenced | parent_query_id | root_query_id
读取操作通过前五列进行跟踪,而最后一列(即 objects_modified
列)指定涉及 Snowflake 列、表和暂存区的数据写入信息。
Snowflake 中的查询及数据库对象的创建方式决定了 Snowflake 在 direct_objects_accessed
、base_objects_accessed
和 objects_modified
列中返回的信息。
同样,如果查询引用受行访问策略保护的对象或者受掩码策略保护的列,Snowflake 会在 policies_referenced
列中记录策略信息。
object_modified_by_ddl
列记录对数据库、架构、表、视图和列执行的 DDL 操作。这些操作还包括针对表或视图指定行访问策略、针对列指定掩码策略以及针对对象或列指定标签更新(例如设置标签、更改标签值)的语句。
parent_query_id
和 root_query_id
列记录对应于以下内容的查询 IDs:
对另一个对象执行读取或写入操作的查询。
对调用存储过程的对象执行读取或写入操作的查询,包括嵌套存储过程调用。有关详细信息,请参阅 祖先查询 (本主题内容)。
有关列详细信息,请参阅 ACCESS_HISTORY 视图中的 Columns 部分。
读取¶
为了解读取查询以及 ACCESS_HISTORY 视图如何记录此信息,请考虑以下场景:
一系列对象:
base_table
»view_1
»view_2
»view_3
。对于
view_2
的读取查询,例如:select * from view_2;
在本示例中,Snowflake 返回的是:
direct_objects_accessed
列中的view_2
,因为查询指定了view_2
。base_objects_accessed
列中的base_table
,因为这是view_2
中数据的原始来源。
请注意, view_1
和 view_3
未包含在 direct_objects_accessed
和 base_objects_accessed
列中,因为查询中并未包含这些视图,它们也不是用作 view_2
中数据来源的基本对象。
写入¶
为了解写入操作以及 ACCESS_HISTORY 视图如何记录此信息,请考虑以下场景:
一个数据源:
base_table
基于该数据源创建一个表(即 CTAS):
create table table_1 as select * from base_table;
在本示例中,Snowflake 返回的是:
base_objects_accessed
和direct_objects_accessed
列中的base_table
,因为该表是直接访问的,并且是数据的来源。objects_modified
列中的table_1
,包括在创建表时写入的列。
列沿袭¶
列沿袭(即列的访问历史记录)扩展了 Account Usage ACCESS_HISTORY 视图,以指定在写入操作中数据如何从源列流向目标列。Snowflake 会通过引用源列数据的所有后续表对象(例如 INSERT、MERGE、CTAS)跟踪源列中的数据,前提是沿袭链中的对象未被删除。Snowflake 通过增强 ACCESS_HISTORY 视图中的 objects_modified
列来讲列沿袭置于可访问状态。
有关更多详细信息,请参阅:
掩码和行访问策略引用¶
POLICY_REFERENCED 列指定在表上设置了行访问策略或在列上设置了掩码策略的对象,包括受行访问策略或掩码策略保护的任何中间对象。Snowflake 记录在表或列上强制实施的策略。
考虑以下对象:
t1
» v1
» v2
其中:
t1
是基表。v1
是基于基表构建的视图。v2
是基于v1
构建的视图。
如果用户查询 v2
,则 policies_referenced
列会记录保护 v2
的行访问策略、保护 v2
中的列的各掩码策略,或同时记录这两种策略(如果适用)。此外,此列还会记录保护 t1
和 v1
的任何掩码或行访问策略。
这些记录有助于数据治理者了解如何访问其受策略保护的对象。
policies_referenced
列还为 ACCESS_HISTORY 视图提供了额外的优势:
识别用户在给定查询中访问的受策略保护的对象。
简化策略的审核流程。
查询 ACCESS_HISTORY 视图后,无需对其他 Account Usage 视图(例如 POLICY_REFERENCES 和 QUERY_HISTORY)进行复杂联接,即可获取有关用户访问的受保护对象和受保护列的信息。
支持的操作¶
有关 ACCESS_HISTORY 视图支持的读取和写入操作的完整说明,请参阅 ACCESS_HISTORY 视图 中的使用说明部分。
优势¶
Snowflake 中的访问历史记录提供了与读取和写入操作相关的以下优势:
- 数据发现:
发现未使用的数据,以确定存档还是删除数据。
- 跟踪敏感数据的移动方式:
跟踪数据从外部云存储位置(例如 Amazon S3 桶)到目标 Snowflake 表的移动,反之亦然。
跟踪内部数据从一个 Snowflake 表到其他 Snowflake 表的移动。
跟踪敏感数据的移动后,应用策略(掩码 和 行访问 策略)以保护数据,更新 访问控制设置 以进一步规范对暂存区和表的访问,并设置 标签 以确保可跟踪包含敏感数据的暂存区、表和列,确保满足合规性要求。
- 数据验证:
报告、仪表板和数据可视化产品(如图表和图形)的准确性和完整性得到验证,因为数据可追溯到其原始来源。
数据专员还可在删除或更改给定表或视图之前通知用户。
- 合规性审计:
标识在表或暂存区上执行写入操作的 Snowflake 用户,以及执行写入操作的时间,以满足合规性法规,例如 GDPR (https://gdpr-info.eu/) 和 CCPA (https://oag.ca.gov/privacy/ccpa)。
- 增强整体数据治理:
ACCESS_HISTORY 视图提供了有关访问哪些数据、何时发生数据访问以及所访问的数据如何从数据源对象移动到数据目标对象的更统一的视图。
列沿袭具有以下额外优势:
- 保护派生的对象:
数据专员可以轻松地为敏感源列添加 标签,而无需在创建派生对象(例如 CTAS)后执行额外的工作。随后,数据专员可以使用 行访问策略 保护包含敏感列的表,或者使用 掩码策略 或 基于标签的掩码策略 来保护敏感列本身。
- 敏感列的复制频率:
数据隐私官可快速确定包含敏感数据的列的对象数量(例如 1 个表、2 个视图)。通过了解包含敏感数据的列在表对象中出现的次数,数据隐私官即可证明他们满足监管合规标准(例如,满足《欧盟通用数据保护条例》(GDPR) 标准)的情况。
- 根本原因分析:
列沿袭提供了一种追溯数据直至其源的机制,这有助于查明由于数据质量差而导致的故障点,并减少故障排除过程中要分析的列数。
查询 ACCESS_HISTORY 视图¶
以下各部分提供了视图的 ACCESS_HISTORY 查询示例。
请注意,某些查询示例会对 query_start_time
列进行筛选,以提高查询性能。提高性能的另一种选择是在较窄的时间范围内执行查询。
示例:读取查询¶
以下小节详细介绍了如何查询 ACCESS_HISTORY 视图,以获取以下用例的读取操作:
获取特定用户的访问历史记录。
根据
object_id
(例如表 ID),促进对过去 30 天内敏感数据访问的合规性审计,以回答以下问题:谁访问了数据?
是在何时访问数据的?
访问了哪些列?
返回用户访问历史记录¶
返回用户访问历史记录,按用户和查询开始时间排序,从最近的访问开始。
SELECT user_name , query_id , query_start_time , direct_objects_accessed , base_objects_accessed FROM access_history ORDER BY 1, 3 desc ;
促进合规性审计¶
以下示例有助于促进合规性审计:
添加
object_id
值,以确定在过去 30 天内谁访问过敏感表:SELECT distinct user_name FROM access_history , lateral flatten(base_objects_accessed) f1 WHERE f1.value:"objectId"::int=<fill_in_object_id> AND f1.value:"objectDomain"::string='Table' AND query_start_time >= dateadd('day', -30, current_timestamp()) ;
使用
32998411400350
的object_id
值,确定过去 30 天内发生访问的时间:SELECT query_id , query_start_time FROM access_history , lateral flatten(base_objects_accessed) f1 WHERE f1.value:"objectId"::int=32998411400350 AND f1.value:"objectDomain"::string='Table' AND query_start_time >= dateadd('day', -30, current_timestamp()) ;
使用
32998411400350
的object_id
值,确定过去 30 天内访问过哪些列:SELECT distinct f4.value AS column_name FROM access_history , lateral flatten(base_objects_accessed) f1 , lateral flatten(f1.value) f2 , lateral flatten(f2.value) f3 , lateral flatten(f3.value) f4 WHERE f1.value:"objectId"::int=32998411400350 AND f1.value:"objectDomain"::string='Table' AND f4.key='columnName' ;
示例:写入操作¶
以下小节详细介绍了如何查询 ACCESS_HISTORY 视图,以获取以下用例的写入操作:
将数据从暂存区加载到表中。
将数据从表卸载到暂存区中。
使用 PUT 命令将本地文件上传到暂存区。
使用 GET 命令将数据文件从暂存区检索到本地目录。
跟踪敏感的暂存区数据移动。
将数据从暂存区加载到表¶
将一组值从外部云存储中的数据文件加载到目标表的列中。
copy into table1(col1, col2) from (select t.$1, t.$2 from @mystage1/data1.csv.gz);
direct_objects_accessed
和 base_objects_accessed
列指定已访问外部命名暂存区:
{ "objectDomain": STAGE "objectName": "mystage1", "objectId": 1, "stageKind": "External Named" }
objects_modified
列指定将数据写入表的两列之中:
{ "columns": [ { "columnName": "col1", "columnId": 1 }, { "columnName": "col2", "columnId": 2 } ], "objectId": 1, "objectName": "TEST_DB.TEST_SCHEMA.TABLE1", "objectDomain": TABLE }
将数据从表卸载到暂存区¶
将一组值从 Snowflake 表卸载到云存储。
copy into @mystage1/data1.csv from table1;
direct_objects_accessed
和 base_objects_accessed
列指定所访问的表列:
{ "objectDomain": TABLE "objectName": "TEST_DB.TEST_SCHEMA.TABLE1", "objectId": 123, "columns": [ { "columnName": "col1", "columnId": 1 }, { "columnName": "col2", "columnId": 2 } ] }
objects_modified
列指定要将访问的数据写入到的暂存区:
{ "objectId": 1, "objectName": "mystage1", "objectDomain": STAGE, "stageKind": "External Named" }
使用 PUT 命令将本地文件上传到暂存区¶
将数据文件复制到内部(即 Snowflake)暂存区。
put file:///tmp/data/mydata.csv @my_int_stage;
direct_objects_accessed
和 base_objects_accessed
列指定所访问文件的本地路径:
{ "location": "file:///tmp/data/mydata.csv" }
objects_modified
列指定要将访问的数据写入的暂存区:
{ "objectId": 1, "objectName": "my_int_stage", "objectDomain": STAGE, "stageKind": "Internal Named" }
使用 GET 命令将数据文件从暂存区检索到本地目录¶
将数据文件从内部暂存区检索到本地计算机上的目录。
get @%mytable file:///tmp/data/;
direct_objects_accessed
和 base_objects_accessed
列指定访问的暂存区和本地目录:
{ "objectDomain": Stage "objectName": "mytable", "objectId": 1, "stageKind": "Table" }
objects_modified
列指定将访问的数据写入到的目录:
{ "location": "file:///tmp/data/" }
跟踪敏感的暂存区数据移动¶
跟踪敏感的暂存区数据,了解它按时间顺序执行的一系列查询。
执行以下查询。请注意,其中 5 个语句会访问暂存区数据。因此,在查询 ACCESS_HISTORY 视图以了解暂存区访问权限时,结果集应包含五行。
use test_db.test_schema; create or replace table T1(content variant); insert into T1(content) select parse_json('{"name": "A", "id":1}'); -- T1 -> T6 insert into T6 select * from T1; -- S1 -> T1 copy into T1 from @S1; -- T1 -> T2 create table T2 as select content:"name" as name, content:"id" as id from T1; -- T1 -> S2 copy into @S2 from T1; -- S1 -> T3 create or replace table T3(customer_info variant); copy into T3 from @S1; -- T1 -> T4 create or replace table T4(name string, id string, address string); insert into T4(name, id) select content:"name", content:"id" from T1; -- T6 -> T7 create table T7 as select * from T6;其中:
T1
、T2
...T7
指定表的名称。
S1
和S2
指定暂存区名称。
查询访问历史记录,以确定对 S1
暂存区的访问。
下表显示了
direct_objects_accessed
、base_objects_accessed
和objects_modified
列的数据。
direct_objects_accessed
base_objects_accessed
objects_modified
[ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68611, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66566, "objectName": "TEST_DB.TEST_SCHEMA.T6" } ] [ { "objectDomain": "Stage", "objectId": 117, "objectName": "TEST_DB.TEST_SCHEMA.S1", "stageKind": "External Named" } ] [ { "objectDomain": "Stage", "objectId": 117, "objectName": "TEST_DB.TEST_SCHEMA.S1", "stageKind": "External Named" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68613, "columnName": "ID" }, { "columnId": 68612, "columnName": "NAME" } ], "objectDomain": "Table", "objectId": 66568, "objectName": "TEST_DB.TEST_SCHEMA.T2" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "objectDomain": "Stage", "objectId": 118, "objectName": "TEST_DB.TEST_SCHEMA.S2", "stageKind": "External Named" } ] [ { "objectDomain": "Stage", "objectId": 117, "objectName": "TEST_DB.TEST_SCHEMA.S1", "stageKind": "External Named" } ] [ { "objectDomain": "Stage", "objectId": 117, "objectName": "TEST_DB.TEST_SCHEMA.S1", "stageKind": "External Named" } ] [ { "columns": [ { "columnId": 68614, "columnName": "CUSTOMER_INFO" } ], "objectDomain": "Table", "objectId": 66570, "objectName": "TEST_DB.TEST_SCHEMA.T3" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68615, "columnName": "NAME" }, { "columnId": 68616, "columnName": "ID" } ], "objectDomain": "Table", "objectId": 66572, "objectName": "TEST_DB.TEST_SCHEMA.T4" } ] [ { "columns": [ { "columnId": 68611, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66566, "objectName": "TEST_DB.TEST_SCHEMA.T6" } ] [ { "columns": [ { "columnId": 68611, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66566, "objectName": "TEST_DB.TEST_SCHEMA.T6" } ] [ { "columns": [ { "columnId": 68618, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66574, "objectName": "TEST_DB.TEST_SCHEMA.T7" } ]请注意有关查询示例的以下方面:
使用 递归公共表表达式。
with access_history_flatten as ( select r.value:"objectId" as source_id, r.value:"objectName" as source_name, r.value:"objectDomain" as source_domain, w.value:"objectId" as target_id, w.value:"objectName" as target_name, w.value:"objectDomain" as target_domain, c.value:"columnName" as target_column, t.query_start_time as query_start_time from (select * from TEST_DB.ACCOUNT_USAGE.ACCESS_HISTORY) t, lateral flatten(input => t.BASE_OBJECTS_ACCESSED) r, lateral flatten(input => t.OBJECTS_MODIFIED) w, lateral flatten(input => w.value:"columns", outer => true) c ), sensitive_data_movements(path, target_id, target_name, target_domain, target_column, query_start_time) as -- Common Table Expression ( -- Anchor Clause: Get the objects that access S1 directly select f.source_name || '-->' || f.target_name as path, f.target_id, f.target_name, f.target_domain, f.target_column, f.query_start_time from access_history_flatten f where f.source_domain = 'Stage' and f.source_name = 'TEST_DB.TEST_SCHEMA.S1' and f.query_start_time >= dateadd(day, -30, date_trunc(day, current_date)) union all -- Recursive Clause: Recursively get all the objects that access S1 indirectly select sensitive_data_movements.path || '-->' || f.target_name as path, f.target_id, f.target_name, f.target_domain, f.target_column, f.query_start_time from access_history_flatten f join sensitive_data_movements on f.source_id = sensitive_data_movements.target_id and f.source_domain = sensitive_data_movements.target_domain and f.query_start_time >= sensitive_data_movements.query_start_time ) select path, target_name, target_id, target_domain, array_agg(distinct target_column) as target_columns from sensitive_data_movements group by path, target_id, target_name, target_domain;该查询生成以下与
S1
暂存区数据移动相关的结果集:
PATH
TARGET_NAME
TARGET_ID
TARGET_DOMAIN
TARGET_COLUMNS
TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1
TEST_DB.TEST_SCHEMA.T1
66564
表
["CONTENT"]
TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1-->TEST_DB.TEST_SCHEMA.S2
TEST_DB.TEST_SCHEMA.S2
118
暂存区
[]
TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1-->TEST_DB.TEST_SCHEMA.T2
TEST_DB.TEST_SCHEMA.T2
66568
表
["NAME","ID"]
TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1-->TEST_DB.TEST_SCHEMA.T4
TEST_DB.TEST_SCHEMA.T4
66572
表
["ID","NAME"]
TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T3
TEST_DB.TEST_SCHEMA.T3
66570
表
["CUSTOMER_INFO"]
示例:列沿袭¶
下面的示例查询 ACCESS_HISTORY 视图,并使用 FLATTEN 函数展平 objects_modified
列。
作为代表性示例,在 Snowflake 账户中执行以下 SQL 查询以生成下表,其中编号的注释指示了以下内容:
// 1
:获取directSources
字段和目标列之间的映射。// 2
:获取baseSources
字段和目标列之间的映射。
// 1
select
directSources.value: "objectId" as source_object_id,
directSources.value: "objectName" as source_object_name,
directSources.value: "columnName" as source_column_name,
'DIRECT' as source_column_type,
om.value: "objectName" as target_object_name,
columns_modified.value: "columnName" as target_column_name
from
(
select
*
from
snowflake.account_usage.access_history
) t,
lateral flatten(input => t.OBJECTS_MODIFIED) om,
lateral flatten(input => om.value: "columns", outer => true) columns_modified,
lateral flatten(
input => columns_modified.value: "directSources",
outer => true
) directSources
union
// 2
select
baseSources.value: "objectId" as source_object_id,
baseSources.value: "objectName" as source_object_name,
baseSources.value: "columnName" as source_column_name,
'BASE' as source_column_type,
om.value: "objectName" as target_object_name,
columns_modified.value: "columnName" as target_column_name
from
(
select
*
from
snowflake.account_usage.access_history
) t,
lateral flatten(input => t.OBJECTS_MODIFIED) om,
lateral flatten(input => om.value: "columns", outer => true) columns_modified,
lateral flatten(
input => columns_modified.value: "baseSources",
outer => true
) baseSources
;
返回:
SOURCE_OBJECT_ID
SOURCE_OBJECT_NAME
SOURCE_COLUMN_NAME
SOURCE_COLUMN_TYPE
TARGET_OBJECT_NAME
TARGET_COLUMN_NAME
1
D.S.T0
NAME
BASE
D.S.T1
NAME
2
D.S.V1
NAME
DIRECT
D.S.T1
NAME
示例:跟踪行访问策略引用¶
在表、视图或物化视图上设置行访问策略且无重复时,为每个实例返回一行:
use role accountadmin; select distinct obj_policy.value:"policyName"::VARCHAR as policy_name from snowflake.account_usage.access_history as ah , lateral flatten(ah.policies_referenced) as obj , lateral flatten(obj.value:"policies") as obj_policy ;
示例:跟踪掩码策略引用¶
在掩码策略保护无重复的列时,为每个实例返回一行。请注意,policies_referenced
列指定的列掩码策略比表上的行访问策略深一级,因此必须执行额外的展平:
use role accountadmin; select distinct policies.value:"policyName"::VARCHAR as policy_name from snowflake.account_usage.access_history as ah , lateral flatten(ah.policies_referenced) as obj , lateral flatten(obj.value:"columns") as columns , lateral flatten(columns.value:"policies") as policies ;
示例:跟踪查询中强制执行的策略¶
返回给定时间范围内给定查询的策略更新时间 (POLICY_CHANGED_TIME) 和策略条件 (POLICY_BODY)。
在使用此查询之前,更新 WHERE 子句输入值:
where query_start_time > '2023-07-07' and
query_start_time < '2023-07-08' and
query_id = '01ad7987-0606-6e2c-0001-dd20f12a9777')
其中:
query_start_time > '2023-07-07'
指定开始时间戳。
query_start_time < '2023-07-08'
指定结束时间戳。
query_id = '01ad7987-0606-6e2c-0001-dd20f12a9777'
指定 Account Usage ACCESS_HISTORY 视图中的查询标识符。
运行查询:
SELECT *
from(
select j1.*,j2.QUERY_START_TIME as POLICY_CHANGED_TIME, POLICY_BODY
from
(
select distinct t1.*,
t4.value:"policyId"::number as PID
from (select *
from SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY
where query_start_time > '2023-07-07' and
query_start_time < '2023-07-08' and
query_id = '01ad7987-0606-6e2c-0001-dd20f12a9777') as t1, //
lateral flatten (input => t1.POLICIES_REFERENCED,OUTER => TRUE) t2,
lateral flatten (input => t2.value:"columns", OUTER => TRUE) t3,
lateral flatten (input => t3.value:"policies",OUTER => TRUE) t4
) as j1
left join
(
select OBJECT_MODIFIED_BY_DDL:"objectId"::number as PID,
QUERY_START_TIME,
OBJECT_MODIFIED_BY_DDL:"properties"."policyBody"."value" as POLICY_BODY
from SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY
where OBJECT_MODIFIED_BY_DDL is not null and
(OBJECT_MODIFIED_BY_DDL:"objectDomain" ilike '%masking%' or OBJECT_MODIFIED_BY_DDL:"objectDomain" ilike '%row%')
) as j2
On j1.POLICIES_REFERENCED is not null and j1.pid = j2.pid and j1.QUERY_START_TIME>j2.QUERY_START_TIME) as j3
QUALIFY ROW_NUMBER() OVER (PARTITION BY query_id,pid ORDER BY policy_changed_time DESC) = 1;
示例: UDFs¶
这些 UDF 示例显示了 Account Usage ACCESS_HISTORY 视图如何记录以下项:
调用 UDF¶
考虑以下 SQL UDF,它计算两个数字的乘积,并假设它存储在名为 mydb.udfs
的架构中:
CREATE FUNCTION MYDB.UDFS.GET_PRODUCT(num1 number, num2 number) RETURNS number AS $$ NUM1 * NUM2 $$ ;
直接 调用 get_product
会在 direct_objects_accessed
列中记录 UDF 详细信息:
[ { "objectDomain": "FUNCTION", "objectName": "MYDB.UDFS.GET_PRODUCT", "objectId": "2", "argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)", "dataType": "NUMBER(38,0)" } ]
此示例类似于 调用存储过程 (本主题内容)。
UDF 及 INSERT DML¶
考虑以下 INSERT 语句,它用于更新名为 mydb.tables.t1
的表中名为 1 和 2 的列:
insert into t1(product) select get_product(c1, c2) from mydb.tables.t1;
ACCESS_HISTORY 视图将 get_product
函数记录在以下位置:
direct_objects_accessed
列,因为函数在语句中 SQL 显式指定,以及directSources
数组中的objects_modified
列,因为函数是插入到列中的值的来源。
同样, t1
表记录在以下列中:
direct_objects_accessed
objects_modified
[ { "objectDomain": "FUNCTION", "objectName": "MYDB.UDFS.GET_PRODUCT", "objectId": "2", "argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)", "dataType": "NUMBER(38,0)" }, { "objectDomain": "TABLE", "objectName": "MYDB.TABLES.T1", "objectId": 1, "columns": [ { "columnName": "c1", "columnId": 1 }, { "columnName": "c2", "columnId": 2 } ] } ] [ { "objectDomain": "TABLE", "objectName": "MYDB.TABLES.T1", "objectId": 2, "columns": [ { "columnId": "product", "columnName": "201", "directSourceColumns": [ { "objectDomain": "Table", "objectName": "MYDB.TABLES.T1", "objectId": "1", "columnName": "c1" }, { "objectDomain": "Table", "objectName": "MYDB.TABLES.T1", "objectId": "1", "columnName": "c2" }, { "objectDomain": "FUNCTION", "objectName": "MYDB.UDFS.GET_PRODUCT", "objectId": "2", "argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)", "dataType": "NUMBER(38,0)" } ], "baseSourceColumns":[] } ] } ]
示例:跟踪由 DDL 操作修改的对象¶
使用 ALLOWED_VALUES 创建标签¶
创建标签:
create tag governance.tags.pii allowed_values 'sensitive','public';
列值:
{ "objectDomain": "TAG", "objectName": "governance.tags.pii", "objectId": "1", "operationType": "CREATE", "properties": { "allowedValues": { "sensitive": { "subOperationType": "ADD" }, "public": { "subOperationType": "ADD" } } } }
备注
如果在创建标签时没有指定允许的值,则 properties
字段为空数组(即 {}
)。
使用标签和掩码策略创建表¶
创建具有列上的掩码策略、列上的标签和表上的标签的表:
create or replace table hr.data.user_info( email string with masking policy governance.policies.email_mask with tag (governance.tags.pii = 'sensitive') ) with tag (governance.tags.pii = 'sensitive');
列值:
{ "objectDomain": "TABLE", "objectName": "hr.data.user_info", "objectId": "1", "operationType": "CREATE", "properties": { "tags": { "governance.tags.pii": { "subOperationType": "ADD", "objectId": { "value": "1" }, "tagValue": { "value": "sensitive" } } }, "columns": { "email": { objectId: { "value": 1 }, "subOperationType": "ADD", "tags": { "governance.tags.pii": { "subOperationType": "ADD", "objectId": { "value": "1" }, "tagValue": { "value": "sensitive" } } }, "maskingPolicies": { "governance.policies.email_mask": { "subOperationType": "ADD", "objectId": { "value": 2 } } } } } } }
在标签上设置掩码策略¶
在标签上设置掩码策略(即 基于标签的掩码):
alter tag governance.tags.pii set masking policy governance.policies.email_mask;
列值:
{ "objectDomain": "TAG", "objectName": "governance.tags.pii", "objectId": "1", "operationType": "ALTER", "properties": { "maskingPolicies": { "governance.policies.email_mask": { "subOperationType": "ADD", "objectId": { "value": 2 } } } } }
交换表¶
将名为 t2
的表交换为名为 t3
的表:
alter table governance.tables.t2 swap with governance.tables.t3;
请注意视图中的两条不同记录。
记录 1:
{ "objectDomain": "Table", "objectId": 0, "objectName": "GOVERNANCE.TABLES.T2", "operationType": "ALTER", "properties": { "swapTargetDomain": { "value": "Table" }, "swapTargetId": { "value": 0 }, "swapTargetName": { "value": "GOVERNANCE.TABLES.T3" } } }
记录 2:
{ "objectDomain": "Table", "objectId": 0, "objectName": "GOVERNANCE.TABLES.T3", "operationType": "ALTER", "properties": { "swapTargetDomain": { "value": "Table" }, "swapTargetId": { "value": 0 }, "swapTargetName": { "value": "GOVERNANCE.TABLES.T2" } } }
删除掩码策略¶
删除掩码策略:
drop masking policy governance.policies.email_mask;
列值:
{ "objectDomain" : "MASKING_POLICY", "objectName": "governance.policies.email_mask", "objectId" : "1", "operationType": "DROP", "properties" : {} }备注
列值具有代表性,适用于对标签和行访问策略执行的 DROP 操作。
properties
字段是一个空数组,在执行 DROP 操作之前不会提供有关策略的任何信息。
跟踪列上的标签引用¶
查询 object_modified_by_ddl
列以监控标签在列上是如何设置的。
以表管理员的身份在列上设置标签、取消设置标签,然后使用不同的字符串值更新标签:
alter table hr.tables.empl_info alter column email set tag governance.tags.test_tag = 'test'; alter table hr.tables.empl_info alter column email unset tag governance.tags.test_tag; alter table hr.tables.empl_info alter column email set tag governance.tags.data_category = 'sensitive';
以数据工程师的身份更改标签值:
alter table hr.tables.empl_info alter column email set tag governance.tags.data_category = 'public';
查询 ACCESS_HISTORY 视图以监视更改:
select query_start_time, user_name, object_modified_by_ddl:"objectName"::string as table_name, 'EMAIL' as column_name, tag_history.value:"subOperationType"::string as operation, tag_history.key as tag_name, nvl((tag_history.value:"tagValue"."value")::string, '') as value from TEST_DB.ACCOUNT_USAGE.access_history ah, lateral flatten(input => ah.OBJECT_MODIFIED_BY_DDL:"properties"."columns"."EMAIL"."tags") tag_history where true and object_modified_by_ddl:"objectDomain" = 'Table' and object_modified_by_ddl:"objectName" = 'TEST_DB.TEST_SH.T' order by query_start_time asc;
返回:
+-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+ | QUERY_START_TIME | USER_NAME | TABLE_NAME | COLUMN_NAME | OPERATION | TAG_NAME | VALUE | +-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+ | Mon, Feb. 14, 2023 12:01:01 -0600 | TABLE_ADMIN | HR.TABLES.EMPL_INFO | EMAIL | ADD | GOVERNANCE.TAGS.TEST_TAG | test | | Mon, Feb. 14, 2023 12:02:01 -0600 | TABLE_ADMIN | HR.TABLES.EMPL_INFO | EMAIL | DROP | GOVERNANCE.TAGS.TEST_TAG | | | Mon, Feb. 14, 2023 12:03:01 -0600 | TABLE_ADMIN | HR.TABLES.EMPL_INFO | EMAIL | ADD | GOVERNANCE.TAGS.DATA_CATEGORY | sensitive | | Mon, Feb. 14, 2023 12:04:01 -0600 | DATA_ENGINEER | HR.TABLES.EMPL_INFO | EMAIL | ADD | GOVERNANCE.TAGS.DATA_CATEGORY | public | +-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+
示例:调用存储过程¶
考虑以下存储过程,并假定它存储在名为 mydb.procedures
的架构中:
create or replace procedure get_id_value(name string) returns string not null language javascript as $$ var my_sql_command = "select id from A where name = '" + NAME + "'"; var statement = snowflake.createStatement( {sqlText: my_sql_command} ); var result = statement.execute(); result.next(); return result.getColumnValue(1); $$ ;
直接 调用 my_procedure
会导致在 direct_objects_accessed
和 base_objects_accessed
列中记录过程详细信息,如下所示:
[ { "objectDomain": "PROCEDURE", "objectName": "MYDB.PROCEDURES.GET_ID_VALUE", "argumentSignature": "(NAME STRING)", "dataType": "STRING" } ]
此示例类似于 调用 UDF (本主题内容)。
示例:使用存储过程的祖先查询¶
您可以使用 parent_query_id
和 root_query_id
列了解存储过程调用之间的关系。
假设您有三个不同的存储过程语句并按以下顺序运行这些语句:
CREATE OR REPLACE PROCEDURE myproc_child() RETURNS INTEGER LANGUAGE SQL AS $$ BEGIN SELECT * FROM mydb.mysch.mytable; RETURN 1; END $$; CREATE OR REPLACE PROCEDURE myproc_parent() RETURNS INTEGER LANGUAGE SQL AS $$ BEGIN CALL myproc_child(); RETURN 1; END $$; CALL myproc_parent();
对 ACCESS_HISTORY 视图的查询将按如下方式记录信息:
SELECT query_id, parent_query_id, root_query_id, direct_objects_accessed FROM SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY;+----------+-----------------+---------------+-----------------------------------+ | QUERY_ID | PARENT_QUERY_ID | ROOT_QUERY_ID | DIRECT_OBJECTS_ACCESSED | +----------+-----------------+---------------+-----------------------------------+ | 1 | NULL | NULL | [{"objectName": "myproc_parent"}] | | 2 | 1 | 1 | [{"objectName": "myproc_child"}] | | 3 | 2 | 1 | [{"objectName": "mytable"}] | +----------+-----------------+---------------+-----------------------------------+
第一行对应的是调用名为
myproc_parent
的第二个存储过程,如direct_objects_accessed
列所示。parent_query_id
和root_query_id
列返回 NULL,因为您直接调用了此存储过程。第二行对应的是调用名为
myproc_child
的第一个存储过程的查询,如direct_objects_accessed column
所示。parent_query_id
和root_query_id
列返回相同的查询 ID,因为调用myproc_child
的查询是由调用 :code:`myproc_parent`(直接调用)的查询启动的。第三行对应的是访问
myproc_child
过程中名为mytable
的表的查询,如direct_objects_accessed
列中所示。parent_query_id
列返回访问mytable
的查询的查询 ID,对应于调用myproc_child
。该存储过程由调用myproc_parent
启的查询动,该查询显示在root_query_id
列中。