在db表中匿名化和清理字段的最快方法?

时间:2017-03-11 02:01:13

标签: postgresql

出于研究目的(例如外包给第三方数据科学家社区组),我需要导出生产数据库,屏蔽某些敏感字段(例如客户名称,电话号码,地址等)。

现在,由于此order_requests表上的生产数据库大约有5亿行,因此我想将字段nickname屏蔽为nickname_transformed。我怎么快速做到?

order_requests表格结构:

┌──────────────────┬─────────────────────────────┬────────────────────┐
│      Column      │            Type             │Modifiers           │
├──────────────────┼─────────────────────────────┼────────────────────┤
│ id               │ integer                     │ not null default   │
│                  │        nextval('order_requests_id_seq'::regclass)│
│ vehicle_cd       │ integer                     │                    │
│ nickname         │ character varying(255)      │                    │
│ phone_number     │ character varying(255)      │                    │
│ pickup_time      │ timestamp without time zone │                    │

... 20+ fields more ...

└──────────────────┴─────────────────────────────┴────────────────────┘
Indexes:
    "order_requests_pkey" PRIMARY KEY, btree (id)
    ... 15+ indexes more ...
Foreign-key constraints:
    ... 7 foreign keys ...
Referenced by:
    ... 25+ references more ...

我当前使用dblink的实现(完成6个小时,而DB上的CPU仅<10%;独立的数据库仅用于我自己):

CREATE EXTENSION dblink;
ALTER TABLE
    order_requests ADD nickname_transformed VARCHAR;
ALTER TABLE order_requests DISABLE TRIGGER USER;
CREATE OR REPLACE FUNCTION f_update_in_steps()
  RETURNS void AS
$func$
DECLARE
   _step int;   -- size of step
   _cur  int;   -- current ID (starting with minimum)
   _max  int;   -- maximum ID
BEGIN
   SELECT INTO _cur, _max  min(id), max(id) FROM order_requests;
                                         -- 100 slices (steps) hard coded
   _step := ((_max - _cur) / 1000) + 1;  -- rounded, possibly a bit too small
                                         -- +1 to avoid endless loop for 0
   PERFORM dblink_connect('postgres://username:password@localhost:5432/dbname');  -- your foreign server as instructed above

   FOR i IN 0..2000 LOOP                 -- 2000 >> 1000 to make sure we exceed _max
      PERFORM dblink_exec(
       $$UPDATE order_requests
         SET    nickname_transformed = md5(nickname)
         WHERE  id >= $$ || _cur || $$
         AND    id <  $$ || _cur + _step || $$
         AND    true$$);  -- avoid empty update
      _cur := _cur + _step;

      EXIT WHEN _cur > _max;            -- stop when done (never loop till 200)
   END LOOP;

   PERFORM dblink_disconnect();
END
$func$  LANGUAGE plpgsql;

我脑子里还有一些问题:

  1. 如果我创建另一个只包含克隆idnickname字段+空nickname_transformed字段的表格,在那里进行操作,然后复制,会更快吗?返回nickname_transformed
  2. article提到了更复杂的方法来实现结果。这有什么可靠的代码示例吗?如何管理dblink连接并链接到各自的操作,notify
  3. 读过这个:

    首选纯函数/查询,因为必须每1-2个月重做一次这个工作。

3 个答案:

答案 0 :(得分:1)

当我需要更新这么大的表中的所有行时,我使用以下过程:

  • 删除表上的所有索引,只留下外键所需的那些索引;
  • 执行更新 - 因为它不需要更新索引,所以更快;
  • 真空充满了桌子 - 以取回旧行版本所用的空间;
  • 重新创建所有索引 - 这可以并行完成 - 我为此使用xargs -P

md5(nickname)没有提供足够的匿名性 - 通过一些蛮力或彩虹表搜索恢复原始昵称很容易 - 他们没有足够的熵。

你应该做substring(md5(nickname||'some-long-secret-string') for 8)

  • 你的承包商不会知道一个秘密,所以它会消除暴力破坏所有绰号的能力;
  • 截断的哈希对于昵称的可靠恢复来说太短了,即使你的秘密会漏掉。

答案 1 :(得分:0)

为什么不:

update order_requests set nickname = id;

忘了nickname_transformed

如果您必须拥有nickname_transformed,那么对于给定的nickname,它保持相同的模糊值,那么:

create table nickname_hash (
    nickname varchar(255),
    hashed char(32)
);
insert into nickname_hash
select distinct nickname from order_requests;
update nickname_hash set hashed = md5(nickname);
create index idx1 on nickname_hash(nickname, hashed);
update o set
o.nickname_transformed = h.hashed
from order_requests o, nickname_hash h
where o.nickname = h.nickname;

答案 2 :(得分:0)

如果您想使用真实数据,可以使用像https://github.com/joke2k/faker/

这样的脚本将假数据替换为真实数据

这个是在Python中,但类似的脚本以多种语言存在