访问历史记录

本主题介绍了 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
;
Copy
  • 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;
Copy
  • 创建视图需要两个不同的表: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
Copy

读取操作通过前五列进行跟踪,而最后一列(即 objects_modified 列)指定涉及 Snowflake 列、表和暂存区的数据写入信息。

Snowflake 中的查询及数据库对象的创建方式决定了 Snowflake 在 direct_objects_accessedbase_objects_accessedobjects_modified 列中返回的信息。

同样,如果查询引用受行访问策略保护的对象或者受掩码策略保护的列,Snowflake 会在 policies_referenced 列中记录策略信息。

object_modified_by_ddl 列记录对数据库、架构、表、视图和列执行的 DDL 操作。这些操作还包括针对表或视图指定行访问策略、针对列指定掩码策略以及针对对象或列指定标签更新(例如设置标签、更改标签值)的语句。

parent_query_idroot_query_id 列记录对应于以下内容的查询 IDs:

  • 对另一个对象执行读取或写入操作的查询。

  • 对调用存储过程的对象执行读取或写入操作的查询,包括嵌套存储过程调用。有关详细信息,请参阅 祖先查询 (本主题内容)。

有关列详细信息,请参阅 ACCESS_HISTORY 视图中的 Columns 部分。

读取

为了解读取查询以及 ACCESS_HISTORY 视图如何记录此信息,请考虑以下场景:

  • 一系列对象: base_table » view_1 » view_2 » view_3

  • 对于 view_2 的读取查询,例如:

    select * from view_2;
    
    Copy

在本示例中,Snowflake 返回的是:

  • direct_objects_accessed 列中的 view_2,因为查询指定了 view_2

  • base_objects_accessed 列中的 base_table,因为这是 view_2 中数据的原始来源。

请注意, view_1view_3 未包含在 direct_objects_accessedbase_objects_accessed 列中,因为查询中并未包含这些视图,它们也不是用作 view_2 中数据来源的基本对象。

写入

为了解写入操作以及 ACCESS_HISTORY 视图如何记录此信息,请考虑以下场景:

  • 一个数据源: base_table

  • 基于该数据源创建一个表(即 CTAS):

    create table table_1 as select * from base_table;
    
    Copy

在本示例中,Snowflake 返回的是:

  • base_objects_accesseddirect_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 中的列的各掩码策略,或同时记录这两种策略(如果适用)。此外,此列还会记录保护 t1v1 的任何掩码或行访问策略。

这些记录有助于数据治理者了解如何访问其受策略保护的对象。

policies_referenced 列还为 ACCESS_HISTORY 视图提供了额外的优势:

  • 识别用户在给定查询中访问的受策略保护的对象。

  • 简化策略的审核流程。

    查询 ACCESS_HISTORY 视图后,无需对其他 Account Usage 视图(例如 POLICY_REFERENCESQUERY_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
;
Copy

促进合规性审计

以下示例有助于促进合规性审计:

  • 添加 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())
    ;
    
    Copy
  • 使用 32998411400350object_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())
    ;
    
    Copy
  • 使用 32998411400350object_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'
    ;
    
    Copy

示例:写入操作

以下小节详细介绍了如何查询 ACCESS_HISTORY 视图,以获取以下用例的写入操作:

  • 将数据从暂存区加载到表中。

  • 将数据从表卸载到暂存区中。

  • 使用 PUT 命令将本地文件上传到暂存区。

  • 使用 GET 命令将数据文件从暂存区检索到本地目录。

  • 跟踪敏感的暂存区数据移动。

将数据从暂存区加载到表

将一组值从外部云存储中的数据文件加载到目标表的列中。

copy into table1(col1, col2)
from (select t.$1, t.$2 from @mystage1/data1.csv.gz);
Copy

direct_objects_accessedbase_objects_accessed 列指定已访问外部命名暂存区:

{
  "objectDomain": STAGE
  "objectName": "mystage1",
  "objectId": 1,
  "stageKind": "External Named"
}
Copy

objects_modified 列指定将数据写入表的两列之中:

{
  "columns": [
     {
       "columnName": "col1",
       "columnId": 1
     },
     {
       "columnName": "col2",
       "columnId": 2
     }
  ],
  "objectId": 1,
  "objectName": "TEST_DB.TEST_SCHEMA.TABLE1",
  "objectDomain": TABLE
}
Copy

将数据从表卸载到暂存区

将一组值从 Snowflake 表卸载到云存储。

copy into @mystage1/data1.csv
from table1;
Copy

direct_objects_accessedbase_objects_accessed 列指定所访问的表列:

{
  "objectDomain": TABLE
  "objectName": "TEST_DB.TEST_SCHEMA.TABLE1",
  "objectId": 123,
  "columns": [
     {
       "columnName": "col1",
       "columnId": 1
     },
     {
       "columnName": "col2",
       "columnId": 2
     }
  ]
}
Copy

objects_modified 列指定要将访问的数据写入到的暂存区:

{
  "objectId": 1,
  "objectName": "mystage1",
  "objectDomain": STAGE,
  "stageKind": "External Named"
}
Copy

使用 PUT 命令将本地文件上传到暂存区

将数据文件复制到内部(即 Snowflake)暂存区。

put file:///tmp/data/mydata.csv @my_int_stage;
Copy

direct_objects_accessedbase_objects_accessed 列指定所访问文件的本地路径:

{
  "location": "file:///tmp/data/mydata.csv"
}
Copy

objects_modified 列指定要将访问的数据写入的暂存区:

{
  "objectId": 1,
  "objectName": "my_int_stage",
  "objectDomain": STAGE,
  "stageKind": "Internal Named"
}
Copy

使用 GET 命令将数据文件从暂存区检索到本地目录

将数据文件从内部暂存区检索到本地计算机上的目录。

get @%mytable file:///tmp/data/;
Copy

direct_objects_accessedbase_objects_accessed 列指定访问的暂存区和本地目录:

{
  "objectDomain": Stage
  "objectName": "mytable",
  "objectId": 1,
  "stageKind": "Table"
}
Copy

objects_modified 列指定将访问的数据写入到的目录:

{
  "location": "file:///tmp/data/"
}
Copy

跟踪敏感的暂存区数据移动

跟踪敏感的暂存区数据,了解它按时间顺序执行的一系列查询。

执行以下查询。请注意,其中 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;
Copy

其中:

  • T1T2 ... T7 指定表的名称。

  • S1S2 指定暂存区名称。

查询访问历史记录,以确定对 S1 暂存区的访问。

下表显示了 direct_objects_accessedbase_objects_accessedobjects_modified 列的数据。

direct_objects_accessed

base_objects_accessed

objects_modified

[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68611,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66566,
    "objectName": "TEST_DB.TEST_SCHEMA.T6"
  }
]
Copy
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
Copy
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68613,
        "columnName": "ID"
      },
      {
        "columnId": 68612,
        "columnName": "NAME"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66568,
    "objectName": "TEST_DB.TEST_SCHEMA.T2"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "objectDomain": "Stage",
    "objectId": 118,
    "objectName": "TEST_DB.TEST_SCHEMA.S2",
    "stageKind": "External Named"
  }
]
Copy
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
Copy
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68614,
        "columnName": "CUSTOMER_INFO"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66570,
    "objectName": "TEST_DB.TEST_SCHEMA.T3"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68615,
        "columnName": "NAME"
      },
      {
        "columnId": 68616,
        "columnName": "ID"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66572,
    "objectName": "TEST_DB.TEST_SCHEMA.T4"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68611,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66566,
    "objectName": "TEST_DB.TEST_SCHEMA.T6"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68611,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66566,
    "objectName": "TEST_DB.TEST_SCHEMA.T6"
  }
]
Copy
[
  {
    "columns": [
      {
        "columnId": 68618,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66574,
    "objectName": "TEST_DB.TEST_SCHEMA.T7"
  }
]
Copy

请注意有关查询示例的以下方面:

  • 使用 递归公共表表达式

  • 使用 JOIN 结构而非 USING 子句

    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;
    
    Copy

该查询生成以下与 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
;
Copy

返回:

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
;
Copy

示例:跟踪掩码策略引用

在掩码策略保护无重复的列时,为每个实例返回一行。请注意,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
;
Copy

示例:跟踪查询中强制执行的策略

返回给定时间范围内给定查询的策略更新时间 (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')
Copy

其中:

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;
Copy

示例: UDFs

这些 UDF 示例显示了 Account Usage ACCESS_HISTORY 视图如何记录以下项:

  • 调用 名为 get_product 的 UDF。

  • 将调用 get_product 函数获得的乘积 插入 到名为 mydb.tables.t1 的表中。

  • 共享 UDFs。

调用 UDF

考虑以下 SQL UDF,它计算两个数字的乘积,并假设它存储在名为 mydb.udfs 的架构中:

CREATE FUNCTION MYDB.UDFS.GET_PRODUCT(num1 number, num2 number)
RETURNS number
AS
$$
    NUM1 * NUM2
$$
;
Copy

直接 调用 get_product 会在 direct_objects_accessed 列中记录 UDF 详细信息:

[
  {
    "objectDomain": "FUNCTION",
    "objectName": "MYDB.UDFS.GET_PRODUCT",
    "objectId": "2",
    "argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)",
    "dataType": "NUMBER(38,0)"
  }
]
Copy

此示例类似于 调用存储过程 (本主题内容)。

UDF 及 INSERT DML

考虑以下 INSERT 语句,它用于更新名为 mydb.tables.t1 的表中名为 1 和 2 的列:

insert into t1(product)
select get_product(c1, c2) from mydb.tables.t1;
Copy

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
      }
    ]
  }
]
Copy
 [
   {
     "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":[]
       }
     ]
   }
]
Copy

共享 UDFs

可以直接或间接引用共享 UDFs :

  • 直接引用与 显式调用 UDF 相同(本主题内容),但会导致 UDF 记录到 base_objects_accesseddirect_objects_accessed 列中。

  • 间接引用的一个示例是调用 UDF 以创建视图:

    create view v as
    select get_product(c1, c2) as vc from t;
    
    Copy

    base_objects_accessed 列记录 UDF 和表。

    direct_objects_accessed 列记录视图。

示例:跟踪由 DDL 操作修改的对象

使用 ALLOWED_VALUES 创建标签

创建标签:

create tag governance.tags.pii allowed_values 'sensitive','public';
Copy

列值:

{
  "objectDomain": "TAG",
  "objectName": "governance.tags.pii",
  "objectId": "1",
  "operationType": "CREATE",
  "properties": {
    "allowedValues": {
      "sensitive": {
        "subOperationType": "ADD"
      },
      "public": {
        "subOperationType": "ADD"
      }
    }
  }
}
Copy

备注

如果在创建标签时没有指定允许的值,则 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');
Copy

列值:

{
  "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
            }
          }
        }
      }
    }
  }
}
Copy

在标签上设置掩码策略

在标签上设置掩码策略(即 基于标签的掩码):

alter tag governance.tags.pii set masking policy governance.policies.email_mask;
Copy

列值:

{
  "objectDomain": "TAG",
  "objectName": "governance.tags.pii",
  "objectId": "1",
  "operationType": "ALTER",
  "properties": {
    "maskingPolicies": {
      "governance.policies.email_mask": {
        "subOperationType": "ADD",
        "objectId": {
          "value": 2
        }
      }
    }
  }
}
Copy

交换表

将名为 t2 的表交换为名为 t3 的表:

alter table governance.tables.t2 swap with governance.tables.t3;
Copy

请注意视图中的两条不同记录。

记录 1:

{
  "objectDomain": "Table",
  "objectId": 0,
  "objectName": "GOVERNANCE.TABLES.T2",
  "operationType": "ALTER",
  "properties": {
    "swapTargetDomain": {
      "value": "Table"
    },
    "swapTargetId": {
      "value": 0
    },
    "swapTargetName": {
      "value": "GOVERNANCE.TABLES.T3"
    }
  }
}
Copy

记录 2:

{
  "objectDomain": "Table",
  "objectId": 0,
  "objectName": "GOVERNANCE.TABLES.T3",
  "operationType": "ALTER",
  "properties": {
    "swapTargetDomain": {
      "value": "Table"
    },
    "swapTargetId": {
      "value": 0
    },
    "swapTargetName": {
      "value": "GOVERNANCE.TABLES.T2"
    }
  }
}
Copy

删除掩码策略

删除掩码策略:

drop masking policy governance.policies.email_mask;
Copy

列值:

{
  "objectDomain" : "MASKING_POLICY",
  "objectName": "governance.policies.email_mask",
  "objectId" : "1",
  "operationType": "DROP",
  "properties" : {}
}
Copy

备注

列值具有代表性,适用于对标签和行访问策略执行的 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';
Copy

以数据工程师的身份更改标签值:

alter table hr.tables.empl_info
  alter column email set tag governance.tags.data_category = 'public';
Copy

查询 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;
Copy

返回:

+-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+
| 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);
$$
;
Copy

直接 调用 my_procedure 会导致在 direct_objects_accessedbase_objects_accessed 列中记录过程详细信息,如下所示:

[
  {
    "objectDomain": "PROCEDURE",
    "objectName": "MYDB.PROCEDURES.GET_ID_VALUE",
    "argumentSignature": "(NAME STRING)",
    "dataType": "STRING"
  }
]
Copy

此示例类似于 调用 UDF (本主题内容)。

示例:使用存储过程的祖先查询

您可以使用 parent_query_idroot_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();
Copy

对 ACCESS_HISTORY 视图的查询将按如下方式记录信息:

SELECT
  query_id,
  parent_query_id,
  root_query_id,
  direct_objects_accessed
FROM
  SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY;
Copy
+----------+-----------------+---------------+-----------------------------------+
| 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_idroot_query_id 列返回 NULL,因为您直接调用了此存储过程。

  • 第二行对应的是调用名为 myproc_child 的第一个存储过程的查询,如 direct_objects_accessed column 所示。

    parent_query_idroot_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 列中。

语言: 中文