访问历史记录

本主题介绍了 Snowflake 中用户访问历史记录的概念。

概述

Snowflake 中的访问历史记录是指用户查询读取数据的时间,以及 SQL 语句执行从源数据对象到目标数据对象的数据写入操作(如 INSERT、UPDATE、DELETE 以及 COPY 命令变体)的时间。通过查询 ACCOUNT_USAGE 和 ORGANIZATION_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 支持的读取和写入操作的详细信息,请参阅视图的 使用说明

跟踪读取和写入操作

ACCOUNT_USAGE 和 ORGANIZATION_USAGE 架构中的 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,包括在创建表时写入的列。

支持的操作

有关 ACCESS_HISTORY 视图支持的读取和写入操作的完整说明,请参阅 ACCESS_HISTORY 视图 中的使用说明部分。

单个请求中的多个语句。

Snowflake 支持将多个语句作为单个请求同时执行。在访问历史记录中跟踪请求的方式取决于它是在 Snowsight 中执行,还是以编程方式执行。

  • 使用 Snowsight 执行多个语句时,它一次运行一个查询,并返回上次执行的查询的 query_id。您可以在 ACCESS_HISTORY 视图中查找所有已执行的语句及其返回值。

  • Snowflake Python 连接器或 Snowflake SQL API 之类的功能会将多个 SQL 语句合并到一个请求中,并为所有语句返回一个 query_id。这个数字实际上是所有单个语句的父查询 ID。要返回包含请求的每个语句的 query_id,您必须使用 parent_query_id 查询 ACCESS_HISTORY。例如,如果请求返回 query_id = 6789,那么您可以通过执行以下命令返回各个语句的查询 ID:

    SELECT query_id, parent_query_id, direct_objects_accessed
    FROM snowflake.account_usage.access_history
    WHERE parent_query_id = 6789;
    
    Copy

优势

Snowflake 中的访问历史记录提供了与读取和写入操作相关的以下优势:

数据发现:

发现未使用的数据,以确定存档还是删除数据。

跟踪敏感数据的移动方式:

跟踪数据从外部云存储位置(例如 Amazon S3 桶)到目标 Snowflake 表的移动,反之亦然。

跟踪内部数据从一个 Snowflake 表到其他 Snowflake 表的移动。

跟踪敏感数据的移动后,应用策略(掩码行访问 策略)以保护数据,更新 访问控制设置 以进一步规范对暂存区和表的访问,并设置 标签 以确保可跟踪包含敏感数据的暂存区、表和列,确保满足合规性要求。

数据验证:

报告、仪表板和数据可视化产品(如图表和图形)的准确性和完整性得到验证,因为数据可追溯到其原始来源。

数据专员还可在删除或更改给定表或视图之前通知用户。

合规性审计:

标识在表或暂存区上执行写入操作的 Snowflake 用户,以及执行写入操作的时间,以满足合规性法规,例如 GDPR (https://gdpr-info.eu/) 和 CCPA (https://oag.ca.gov/privacy/ccpa)。

增强整体数据治理:

ACCESS_HISTORY 视图提供了有关访问哪些数据、何时发生数据访问以及所访问的数据如何从数据源对象移动到数据目标对象的更统一的视图。

列沿袭

列沿袭(即列的访问历史记录)扩展了 Account Usage ACCESS_HISTORY 视图,以指定在写入操作中数据如何从源列流向目标列。Snowflake 会通过引用源列数据的所有后续表对象(例如 INSERT、MERGE、CTAS)跟踪源列中的数据,前提是沿袭链中的对象未被删除。Snowflake 通过增强 ACCESS_HISTORY 视图中的 objects_modified 列来讲列沿袭置于可访问状态。

列沿袭提供以下优势:

保护派生的对象:

数据专员可以轻松地为敏感源列添加 标签,而无需在创建派生对象(例如 CTAS)后执行额外的工作。随后,数据专员可以使用 行访问策略 保护包含敏感列的表,或者使用 掩码策略基于标签的掩码策略 来保护敏感列本身。

敏感列的复制频率:

数据隐私官可快速确定包含敏感数据的列的对象数量(例如 1 个表、2 个视图)。通过了解包含敏感数据的列在表对象中出现的次数,数据隐私官即可证明他们满足监管合规标准(例如,满足《欧盟通用数据保护条例》(GDPR) 标准)的情况。

根本原因分析:

列沿袭提供了一种追溯数据直至其源的机制,这有助于查明由于数据质量差而导致的故障点,并减少故障排除过程中要分析的列数。

有关列沿袭的其他详细信息,请参阅:

掩码和行访问策略引用

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)进行复杂联接,即可获取有关用户访问的受保护对象和受保护列的信息。

账户级与企业级访问历史记录

管理员通过查询账户 ACCOUNT_USAGE 架构中的 ACCESS_HISTORY 视图,来监控账户级访问历史记录。没有与 ACCOUNT_USAGE.ACCESS_HISTORY 视图关联的任何额外成本。

ORGANIZATION_USAGE 架构中的 ACCESS_HISTORY 视图将组织中所有账户的访问历史记录收集到一个视图中,以提供组织级访问历史记录。此 ORGANIZATION_USAGE.ACCESS_HISTORY 视图只能在 组织账户 中找到。

ORGANIZATION_USAGE 架构中的组织级访问历史记录与 ACCOUNT_USAGE 架构中的访问历史记录在以下方面有所不同:

其他列:

组织账户中的 ORGANIZATION_USAGE.ACCESS_HISTORY 视图包含其他列,这些列提供与 组织列表 相关的见解。这些列可用于确定使用者的查询访问了哪些附加到组织列表的数据产品,以及这些数据产品是否受到策略(例如掩码策略)的保护。有关更多信息,请参阅 组织列表治理

其他成本:

组织账户中的 ORGANIZATION_USAGE.ACCESS_HISTORY 视图是高级视图,会产生以下成本:

  • 与用于填充 ACCESS_HISTORY 视图的 Serverless Task 关联的计算成本。

  • 与在 ACCESS_HISTORY 视图中存储数据关联的存储成本。

有关这些成本的更多信息,请参阅 与高级视图相关的成本

受支持对象

使用下表来确定当 SQL 语句涉及特定类型的对象时,ACCESS_HISTORY 视图是否包含记录。SQL 语句包括:

  • 数据操作语言 (DML) 语句。例如,用于向表中插入数据的语句。

  • 数据查询语言 (DQL) 语句。例如,使用 SELECT 语句投影数据的语句。

  • 数据定义语言 (DDL) 语句。例如,创建或更改 Snowflake 对象的语句。

对象

DML

DQL

DDL

备注

DATABASE

不适用

不适用

DYNAMIC TABLE

部分

仅 ​​``ALTER DYNAMIC TABLE ... REFRESH``​ 命令支持 ​​DML​​ 功能。

EXTERNAL TABLE

FUNCTION

不适用

对 DQL 的支持仅限于出现在 SELECT 语句中的函数。

ICEBERG TABLE

部分

Full support (DML, DQL, DDL) for Snowflake-managed Apache Iceberg™ tables. Support for DQL and DDL only for externally managed Apache Iceberg™ tables.

LISTING

不适用

不适用

MATERIALIZED VIEW

不适用

POLICY

不适用

DDL 支持在策略应用到对象时显示相关信息。DQL 支持在查询运行时显示正在强制执行的策略。

PROCEDURE

不适用

一个过程可以有多个 SQL 语句,每个语句生成单独的记录。

ROLE

不适用

不适用

SCHEMA

不适用

不适用

SEQUENCE

不适用

不支持 DML 是有意为之。

SESSION

不适用

不适用

SHARE

不适用

不适用

STAGE

部分

对 DML 的支持仅限于使用暂存区作为表的源。对于 DQL,不支持针对暂存区的查询。

STREAM

不适用

部分

对 DQL 的支持仅限于使用流作为表的源。

TABLE

TAG

不适用

不适用

VIEW

不适用

查询 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 列中。

示例:序列

考虑以下创建序列的 SQL 语句:

CREATE SEQUENCE SEQ
  START = 2
  INCREMENT = 7
  COMMENT = 'Comment on sequence';
Copy

创建此序列会在访问历史记录中生成以下条目:

{
  "objectDomain": "Sequence",
  "objectId": 1,
  "objectName": "TEST_DB.TEST_SCHEMA.SEQ",
  "operationType": "CREATE",
  "properties": {
    "start": {
      "value": "2"
    },
    "increment": {
        "value": "7"
    },
    "comment": {
          "value": "Comment on Sequence"
    }
  }
}
Copy

示例:联接

查询中的联接操作在访问历史记录中会以 joinObject 的形式显示在 ​​:code:direct_accessed_objects 列中。joinObject 不会出现在其他列中,因为访问历史记录只跟踪查询中明确提及的联接。

例如,考虑以下联接表 t1 和表 t2 的查询:

CREATE OR REPLACE VIEW v1 (vc1, vc2) AS
  SELECT
    t1.c1 AS vc1,
    t2.c2 AS vc2
  FROM t1 LEFT OUTER JOIN t2
    ON t1.c2 = t2.c1;
Copy

执行此查询会导致 direct_accessed_objects 列中的 t1 对象出现以下内容:

{
  "columns": [
    {
      "columnId": 0,
      "columnName": "C1"
    },
    {
      "columnId": 0,
      "columnName": "C2"
    }
  ],
  "joinObjects": [
    {
      "joinType": "LEFT_OUTER_JOIN",
      "node": {
        "objectDomain": "Table",
        "objectId": 0,
        "objectName": "DB1.SCH.T2"
      }
    }
  ],
  "objectDomain": "Table",
  "objectId": 0,
  "objectName": "DB1.SCH.T1"
}
Copy

备注

在这个例子中,访问历史记录中不会包含 ​​``t2``​对象的 ​​``joinObject``​​ 信息,因为该信息已由表 ​​``t1``​​ 的 ​​``joinObject``​​ 提供,属于冗余数据。

语言: 中文