查找有 int4 主键溢出风险的表

如今,现代 ORM 比如 Rails 或 Django 使用 int8 (bigint) 类型的主键 (PK)。然而,在旧项目中,可能存在使用 int4 (integer, int, serial) 主键的老表,这些表可能已增长并存在 int4 溢出的风险 — int4 的最大值为 2,147,483,647,且不停机将主键从 int4 转换为 int8 并不是一项简单的任务 (TODO:在另一篇使用指南中介绍转换过程)。

以下是快速检查是否存在 int2 或 int4 PK 表的方法,并了解在每种情况下已使用了多少"容量" (来自postgres-checkup 的查询):

SQL
do $$
declare
  min_relpages int8 := 0; -- in very large DBs, skip small tables by setting this to 100
  rec record;
  out text := '';
  out1 json;
  i numeric := 0;
  val int8;
  ratio numeric;
  sql text;
begin
  for rec in
    select
      c.oid,
      spcname as tblspace,
      nspname as schema_name,
      relname as table_name,
      t.typname,
      pg_get_serial_sequence(format('%I.%I', nspname, relname), attname) as seq,
      min(attname) as attname
    from pg_index i
    join pg_class c on c.oid = i.indrelid
    left join pg_tablespace tsp on tsp.oid = reltablespace
    left join pg_namespace n on n.oid = c.relnamespace
    join pg_attribute a on
      a.attrelid = i.indrelid
      and a.attnum = any(i.indkey)
    join pg_type t on t.oid = atttypid
    where
      i.indisprimary
      and (
        c.relpages >= min_relpages
        or pg_get_serial_sequence(format('%I.%I', nspname, relname), attname) is not null
      ) and t.typname in ('int2', 'int4')
      and nspname <> 'pg_toast'
      group by 1, 2, 3, 4, 5, 6
      having count(*) = 1 -- skip PKs with 2+ columns (TODO: analyze them too)
  loop
    raise debug 'table: %', rec.table_name;

    if rec.seq is null then
      sql := format('select max(%I) from %I.%I;', rec.attname, rec.schema_name, rec.table_name);
    else
      sql := format('select last_value from %s;', rec.seq);
    end if;

    raise debug 'sql: %', sql;
    execute sql into val;

    if rec.typname = 'int4' then
      ratio := (val::numeric / 2^31)::numeric;
    elsif rec.typname = 'int2' then
      ratio := (val::numeric / 2^15)::numeric;
    else
      assert false, 'unreachable point';
    end if;

    if ratio > 0.1 then -- report only if > 10% of capacity is reached
      i := i + 1;

      out1 := json_build_object(
        'table', (
          coalesce(nullif(quote_ident(rec.schema_name), 'public') || '.', '')
          || quote_ident(rec.table_name)
        ),
        'pk', rec.attname,
        'type', rec.typname,
        'current_max_value', val,
        'capacity_used_percent', round(100 * ratio, 2)
      );

      raise debug 'cur: %', out1;

      if out <> '' then
        out := out || e',\n';
      end if;

      out := out || format('  "%s": %s', rec.table_name, out1);
    end if;
  end loop;

  raise info e'{\n%\n}', out;
end;
$$ language plpgsql;

输出示例

SQL
INFO:  {
  "oldbig": {"table" : "oldbig", "pk" : "id", "type" : "int4", "current_max_value" : 2107480000, "capacity_used_percent" : 98.14},
  "oldbig2": {"table" : "oldbig2", "pk" : "id", "type" : "int4", "current_max_value" : 1107480000, "capacity_used_percent" : 51.57}
}

讨论与补充

在 GitHub 查看讨论

支持作者

感谢支持 PostgreSQL Howto 的长期维护。

微信赞赏码

读者交流群

二维码若已过期,可通过公众号 PostgreSQL 学徒或邮件联系。

PostgreSQL 学徒读者交流群二维码