ALTER TABLE 上的数据库“冻结”

Gon*_*uez 23 postgresql alter-table blocking postgresql-9.4

我们的生产环境今天早上在更改表时冻结*了一段时间,实际上是添加了一列。

违规 SQL:ALTER TABLE cliente ADD COLUMN topicos character varying(20)[];

* 登录到我们的系统需要从同一张表中进行选择,因此在更改表期间没有人可以登录。我们实际上不得不终止进程以让系统恢复正常运行。


表结构:

CREATE TABLE cliente
(
  rut character varying(30) NOT NULL,
  nombre character varying(150) NOT NULL,
  razon_social character varying(150) NOT NULL,
  direccion character varying(200) NOT NULL,
  comuna character varying(100) NOT NULL,
  ciudad character varying(100) NOT NULL,
  codigo_pais character varying(3) NOT NULL,
  activo boolean DEFAULT true,
  id serial NOT NULL,
  stock boolean DEFAULT false,
  vigente boolean DEFAULT true,
  clase integer DEFAULT 1,
  plan integer DEFAULT 1,
  plantilla character varying(15) DEFAULT 'WAYPOINT'::character varying,
  facturable integer DEFAULT 1,
  toolkit integer DEFAULT 0,
  propietario integer DEFAULT 0,
  creacion timestamp without time zone DEFAULT now(),
  codelco boolean NOT NULL DEFAULT false,
  familia integer DEFAULT 0,
  enabled_machines boolean DEFAULT false,
  enabled_canbus boolean DEFAULT false,
  enabled_horometro boolean DEFAULT false,
  enabled_comap boolean DEFAULT false,
  enabled_frio boolean DEFAULT false,
  enabled_panico boolean DEFAULT false,
  enabled_puerta boolean DEFAULT false,
  enabled_rpm boolean DEFAULT false,
  enabled_supervisor integer DEFAULT 0,
  demo boolean,
  interno boolean,
  mqtt_enable boolean NOT NULL DEFAULT false,
  topicos character varying(20)[],
  CONSTRAINT pk_cliente PRIMARY KEY (rut),
  CONSTRAINT fk_cliente_familiaid FOREIGN KEY (familia)
      REFERENCES cliente_familia (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT pk_pais FOREIGN KEY (codigo_pais)
      REFERENCES pais (codigo) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT unique_id_cliente UNIQUE (id)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE cliente
  OWNER TO waypoint;
GRANT ALL ON TABLE cliente TO waypoint;
GRANT ALL ON TABLE cliente TO waypointtx;
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE cliente TO waypointtomcat;
GRANT SELECT ON TABLE cliente TO waypointphp;
GRANT SELECT ON TABLE cliente TO waypointpphppublic;
GRANT ALL ON TABLE cliente TO waypointsoporte;
GRANT SELECT, INSERT ON TABLE cliente TO waypointsalesforce;
GRANT SELECT ON TABLE cliente TO waypointadminuser;
GRANT SELECT ON TABLE cliente TO waypointagenda;
GRANT SELECT ON TABLE cliente TO waypointmachines;
GRANT SELECT ON TABLE cliente TO waypointreports;
GRANT SELECT ON TABLE cliente TO readonly;

CREATE INDEX index_cliente
  ON cliente
  USING btree
  (rut COLLATE pg_catalog."default");

CREATE INDEX index_cliente_activo
  ON cliente
  USING btree
  (activo);

CREATE INDEX index_cliente_id_activo
  ON cliente
  USING btree
  (id, activo);

CREATE INDEX index_cliente_rut_activo
  ON cliente
  USING btree
  (rut COLLATE pg_catalog."default", activo);


CREATE TRIGGER trigger_default_admin
  AFTER INSERT
  ON cliente
  FOR EACH ROW
  EXECUTE PROCEDURE crea_default_admin();

CREATE TRIGGER trigger_default_grupo
  AFTER INSERT
  ON cliente
  FOR EACH ROW
  EXECUTE PROCEDURE crea_default_clientegrupo();  
Run Code Online (Sandbox Code Playgroud)

我应该禁用 CONSTRAINTS、TRIGGERS 还是其他什么?

也许任何数据库调优?

我还应该提供什么以供进一步分析?

版本:PostgreSQL 9.4.5 on x86_64-unknown-linux-gnu,由 gcc (Debian 4.9.2-10) 4.9.2 编译,64 位

jja*_*nes 47

您希望运行的命令确实对该表进行了 ACCESS EXCLUSIVE 锁定,以防止对该表的所有其他访问。但是这个锁的持续时间应该只有几毫秒,因为添加一个像你想添加的列不需要重写表,它只需要更新元数据。

问题可能出现的地方,我敢打赌,这是您所看到的问题,是锁定优先级。有人在那个表上有一个弱锁,比如 ACCESS SHARE 锁,并且他们无限期地驻扎在它上面(可能是一个已泄露的空闲事务连接?打开 psql 的人,以可重复读取模式开始查询,然后去度假?)。

ADD COLUMN 尝试获取它需要的 ACCESS EXCLUSIVE,并在第一个锁后面排队。

现在所有未来的锁定请求都排在等待的 ACCESS EXCLUSIVE 请求之后。

从概念上讲,与已经授予的锁兼容的传入锁请求可以跳过等待的 ACCESS EXCLUSIVE 并被不按顺序授予,但这不是 PostgreSQL 这样做的方式。

您需要找到持有长期弱锁的进程。

您可以通过查询 pg_locks 表来做到这一点。

select * from pg_locks where 
    granted and relation = 'cliente'::regclass \x\g\x
Run Code Online (Sandbox Code Playgroud)

如果你在一切都被锁定的情况下这样做,你应该只会得到一个答案(除非有多个长期存在的罪魁祸首)。如果您在杀死 ADD COLUMN 之后执行此操作,那么您可能会看到许多授予的锁,但是如果您重复几次,每次应该都会留下一个或几个。

然后,您可以获取从 pg_lock 获得的 PID,并将其查询到 pg_stat_activity 中以查看攻击者在做什么:

select * from pg_stat_activity where pid=28731 \x\g\x
Run Code Online (Sandbox Code Playgroud)

...

backend_start    | 2016-03-22 13:08:30.849405-07
xact_start       | 2016-03-22 13:08:36.797703-07
query_start      | 2016-03-22 13:08:36.799021-07
state_change     | 2016-03-22 13:08:36.824369-07
waiting          | f
state            | idle in transaction
backend_xid      |
backend_xmin     |
query            | select * from cliente limit 4;
Run Code Online (Sandbox Code Playgroud)

因此,它在事务中运行了一个查询,然后在没有关闭事务的情况下进入空闲状态。现在是 13 点 13 分,所以他们已经闲置了 5 分钟。

  • 这个答案救了我的命 (10认同)
  • 我的也保存了,关于“锁优先级”的部分非常好,因为我在其他地方没有读过,谢谢! (2认同)

Dav*_*ett 11

DDL 操作通常会锁定它们正在执行的对象,因此不应在计划的维护窗口之外执行(当您的用户预期中断或系统在计划的时间内完全脱机时) - 您无能为力关于这个很容易1

某些操作只保留一个写锁,因此您的应用程序可以继续为仅读取受影响对象的请求提供服务。

该文档似乎非常擅长列出 DDL 操作可能持有哪些锁。

这个博客条目有一个摘要,它表明如果列可以为空并且没有默认值或唯一约束,则添加列可以是在线操作,尽管这意味着您声明的语句应该在没有锁的情况下运行(如 IIRC postgres除非您明确声明,否则默认列是可 NULL 的)。您在添加列之后是否运行了任何其他操作?也许在其上创建一个索引(默认情况下会在表上使用写锁)?

1 一些复制/集群/镜像安排将允许您更新镜像(在更改期间暂停更新并在之后重播),切换到使用该副本作为实时副本,依此类推,直到每个副本都更新,所以停机时间仅限于重放 DDL 操作期间所做更改所需的时间。但是,像这样的实时操作并非没有风险,因此除非您绝对不能,否则建议您安排适当的维护窗口来执行和验证结构更新。