Migraciones de SQL a Postgres. Parte 1



驴C贸mo actualizo el valor del atributo para todos los registros en una tabla? 驴C贸mo agrego una clave principal o 煤nica a una tabla? 驴C贸mo divido una mesa en dos? C贸mo ... 



Si es posible que la aplicaci贸n no est茅 disponible durante alg煤n tiempo para las migraciones, las respuestas a estas preguntas no son dif铆ciles. Pero, 驴qu茅 sucede si necesita migrar en caliente, sin detener la base de datos y sin molestar a otros para que trabajen con ella?



Intentaremos responder a estas y otras preguntas que surgen durante las migraciones de esquemas y datos en PostgreSQL en forma de consejos pr谩cticos.



Este art铆culo: rendimiento de decodificaci贸n en la conferencia SmartDataConf ( aqu铆 puede encontrar la presentaci贸n, el video aparecer谩 a su debido tiempo). Hab铆a mucho texto, por lo que el material se dividir谩 en 2 art铆culos: 



  • migraciones basicas
  • enfoques para actualizar tablas grandes.


Al final hay un resumen de todo el art铆culo en forma de una hoja de trucos de tabla din谩mica.



Contenido



El meollo del problema

Agregar una columna

Agregar una

columna predeterminada Eliminar una columna

Crear un 铆ndice

Crear un 铆ndice en una tabla particionada

Crear una restricci贸n NOT NULL

Crear una clave externa

Crear una restricci贸n 煤nica

Crear una clave primaria Hoja de referencia de

migraci贸n r谩pida



La esencia del problema



Supongamos que tenemos una aplicaci贸n que funciona con una base de datos. En la configuraci贸n m铆nima, puede constar de 2 nodos: la propia aplicaci贸n y la base de datos, respectivamente. 





Con este esquema, las actualizaciones de aplicaciones a menudo ocurren con tiempo de inactividad. Al mismo tiempo, puede actualizar la base de datos. En tal situaci贸n, el criterio principal es el tiempo, es decir, debe completar la migraci贸n lo m谩s r谩pido posible para minimizar el tiempo de indisponibilidad del servicio. 



Si la aplicaci贸n crece y se hace necesario realizar lanzamientos sin tiempo de inactividad, comenzamos a utilizar m煤ltiples servidores de aplicaciones. Puede haber tantos como desee y estar谩n en diferentes versiones. En este caso, es necesario garantizar la compatibilidad con versiones anteriores. 





En la siguiente etapa de crecimiento, los datos dejan de caber en una base de datos. Tambi茅n comenzamos a escalar la base de datos, mediante la fragmentaci贸n. Dado que en la pr谩ctica es muy dif铆cil migrar m煤ltiples bases de datos de forma s铆ncrona, esto significa que en alg煤n momento tendr谩n diferentes esquemas de datos. En consecuencia, trabajaremos en un entorno heterog茅neo, donde los servidores de aplicaciones pueden tener diferentes c贸digos y bases de datos con diferentes esquemas de datos. 





Se trata de esta configuraci贸n de la que hablaremos en este art铆culo y consideraremos las migraciones m谩s populares que escriben los desarrolladores, desde las m谩s simples hasta las m谩s complejas.



Nuestro objetivo es realizar migraciones SQL con un impacto m铆nimo en el rendimiento de la aplicaci贸n, es decir, cambie los datos o el esquema de datos para que la aplicaci贸n contin煤e ejecut谩ndose y los usuarios no se den cuenta. 



Agregar una columna



ALTER TABLE my_table ADD COLUMN new_column INTEGER --   

      
      





Probablemente, cualquier persona que trabaje con la base de datos escribi贸 una migraci贸n similar. Si hablamos de PostgreSQL, entonces esta migraci贸n es muy barata y segura. El comando en s铆, aunque captura el bloqueo de nivel m谩s alto ( AccessExclusive ), se ejecuta muy r谩pidamente, porque bajo el cap贸 solo se agrega metainformaci贸n sobre una nueva columna sin reescribir los datos de la tabla en s铆. En la mayor铆a de los casos, esto pasa desapercibido. Pero pueden surgir problemas si en el momento de la migraci贸n hay transacciones largas trabajando con esta tabla. Para comprender la esencia del problema, veamos un peque帽o ejemplo de c贸mo funcionan los bloqueos de forma simplificada en PostgreSQL. Este aspecto ser谩 muy importante al considerar tambi茅n la mayor铆a de las otras migraciones.



Supongamos que tenemos una tabla grande y SELECCIONAMOS todos los datos de ella. Dependiendo del tama帽o de la base de datos y de la propia tabla, puede tardar varios segundos o incluso minutos. 





El bloqueo de AccessShare m谩s d茅bil que protege contra cambios en la estructura de la tabla se adquiere durante la transacci贸n .



En este momento, llega otra transacci贸n, que solo intenta realizar una consulta ALTER TABLE a esta tabla. El comando ALTER TABLE, como se mencion贸 anteriormente, toma un bloqueo AccessExclusive , que no es compatible con ning煤n otro bloqueo . Ella se pone en fila.



Esta cola de bloqueo se "rastrilla" en estricto orden; incluso si otras consultas vienen despu茅s de ALTER TABLE (por ejemplo, tambi茅n SELECT), que por s铆 mismas no entran en conflicto con la primera consulta, todas se ponen en cola para ALTER TABLE. Como resultado, la aplicaci贸n "se detiene" y espera a que se ejecute ALTER TABLE.



驴Qu茅 hacer en tal situaci贸n? Puede limitar el tiempo que se tarda en adquirir un bloqueo mediante el comando SET lock_timeout . Ejecutamos este comando antes de ALTER TABLE (la palabra clave LOCAL significa que la configuraci贸n es v谩lida solo dentro de la transacci贸n actual, de lo contrario, dentro de la sesi贸n actual):



SET LOCAL lock_timeout TO '100ms'

      
      





y si en 100 milisegundos el comando no logra adquirir el bloqueo, fallar谩. Luego, o lo reiniciamos nuevamente, esperando que sea exitoso, o vamos a averiguar por qu茅 la transacci贸n lleva tanto tiempo, si esto no deber铆a estar en nuestra aplicaci贸n. En cualquier caso, lo principal es que no bloqueamos la aplicaci贸n.



Se debe decir que establecer un tiempo de espera es 煤til antes de cualquier comando que tome un bloqueo estricto.



Agregar una columna con un valor predeterminado



--     PG 11
ALTER TABLE my_table ADD COLUMN new_column INTEGER DEFAULT 42

      
      





Si este comando se ejecuta en una versi贸n anterior de PostgreSQL (por debajo de la 11), sobrescribir谩 todas las filas de la tabla. Obviamente, si la mesa es grande, esto puede llevar mucho tiempo. Y dado que se captura un bloqueo estricto ( AccessExclusive ) durante el tiempo de ejecuci贸n , tambi茅n se bloquean todas las consultas a la tabla. 



Si PostgreSQL es 11 o m谩s reciente, esta operaci贸n es bastante econ贸mica. El caso es que en la versi贸n 11 se hizo una optimizaci贸n, gracias a la cual, en lugar de reescribir la tabla, se almacena el valor por defecto en una tabla especial pg_attribute, y posteriormente, al realizar SELECT, todos los valores vac铆os de esta columna ser谩n ser reemplazado sobre la marcha con este valor. En este caso, m谩s adelante, cuando las filas de la tabla se sobrescriban debido a otras modificaciones, el valor se escribir谩 en estas filas. 



Adem谩s, a partir de la versi贸n 11, tambi茅n puede crear inmediatamente una nueva columna y marcarla como NOT NULL:



--     PG 11
ALTER TABLE my_table ADD COLUMN new_column INTEGER DEFAULT 42 NOT NULL

      
      





驴Qu茅 pasa si PostgreSQL tiene m谩s de 11 a帽os? 



La migraci贸n se puede realizar en varios pasos. Primero, creamos una nueva columna sin restricciones ni valores predeterminados. Como se dijo anteriormente, es barato y r谩pido. En la misma transacci贸n, modificamos esta columna agregando un valor predeterminado.



ALTER TABLE my_table ADD COLUMN new_column INTEGER;
ALTER TABLE my_table ALTER COLUMN new_column SET DEFAULT 42;

      
      





Esta divisi贸n de un comando en dos puede parecer un poco extra帽a, pero la mec谩nica es tal que cuando se crea una nueva columna inmediatamente con un valor predeterminado, afecta a todos los registros que est谩n en la tabla, y cuando el valor se establece para una columna existente (aunque solo sea lo creado, como en nuestro caso), solo afecta a los registros nuevos.



As铆, luego de ejecutar estos comandos, nos queda actualizar los valores que ya estaban en la tabla. En t茅rminos generales, debemos hacer algo como esto:



UPDATE my_table set new_column = 42 --    

      
      





Pero tal ACTUALIZACI脫N "frontal" es realmente imposible, porque al actualizar una tabla grande, bloquearemos toda la tabla durante mucho tiempo. En el segundo art铆culo (aqu铆 en el futuro habr谩 un enlace) veremos qu茅 estrategias existen para actualizar tablas grandes en PostgreSQL, pero por ahora asumiremos que de alguna manera hemos actualizado los datos, y ahora tanto los datos antiguos como el nuevo tendr谩 el valor requerido por defecto. 



Eliminar una columna



ALTER TABLE my_table DROP COLUMN new_column --   

      
      





Aqu铆 la l贸gica es la misma que cuando se agrega una columna: los datos de la tabla no se modifican, solo se cambia la metainformaci贸n. En este caso, la columna se marca como eliminada y no est谩 disponible para consultas. Esto explica el hecho de que cuando se suelta una columna en PostgreSQL, no se libera espacio f铆sico (a menos que realice un VACUUM FULL), es decir, los datos de los registros antiguos a煤n permanecen en la tabla, pero no est谩n disponibles cuando se accede a ellos. La desasignaci贸n se produce gradualmente a medida que se sobrescriben las filas de la tabla.



Por lo tanto, la migraci贸n en s铆 es simple, pero, por regla general, a veces se encuentran errores en el backend. Antes de eliminar una columna, hay que seguir unos sencillos pasos preparatorios.



  • Primero, debe eliminar todas las restricciones (NOT NULL, CHECK, ...) que est谩n en esta columna:

    ALTER TABLE my_table ALTER COLUMN new_column DROP NOT NULL
          
          



  • El siguiente paso es garantizar la compatibilidad con el backend. Debe asegurarse de que la columna no se use en ning煤n lugar. Por ejemplo, en Hibernate, debe marcar un campo mediante una anotaci贸n @Transient



    . En el JOOQ que estamos usando, el campo se agrega a las excepciones usando una etiqueta <excludes>



    :

    <excludes>my_table.new_column</excludes>
          
          





    Tambi茅n debe observar de cerca las consultas "SELECT *"



    : los marcos pueden mapear todas las columnas en una estructura en el c贸digo (y viceversa) y, en consecuencia, puede enfrentar nuevamente el problema de acceder a una columna inexistente.


Una vez publicados los cambios en todos los servidores de aplicaciones, puede eliminar la columna. 



Creaci贸n de 铆ndice



CREATE INDEX my_table_index ON my_table (name) -- ,  

      
      





Aquellos que trabajan con PostgreSQL probablemente sepan que este comando bloquea toda la tabla. Pero desde la muy antigua versi贸n 8.2 existe la palabra clave CONCURRENTLY , que le permite crear un 铆ndice en un modo sin bloqueo.



CREATE CONCURRENTLY INDEX my_table_index ON my_table (name) -- 

      
      





El comando es m谩s lento, pero no interfiere con las solicitudes paralelas.



Este equipo tiene una salvedad. Puede fallar, por ejemplo, al crear un 铆ndice 煤nico en una tabla que contiene valores duplicados. Se crear谩 el 铆ndice, pero se marcar谩 como no v谩lido y no se utilizar谩 en consultas. El estado del 铆ndice se puede verificar con la siguiente consulta: 



SELECT pg_index.indisvalid
    FROM pg_class, pg_index
WHERE pg_index.indexrelid = pg_class.oid
    AND pg_class.relname = 'my_table_index'
      
      





En tal situaci贸n, debe eliminar el 铆ndice anterior, corregir los valores en la tabla y luego volver a crearlo.



DROP INDEX CONCURRENTLY my_table_index
UPDATE my_table ...
CREATE CONCURRENTLY INDEX my_table_index ON my_table (name)

      
      





Es importante tener en cuenta que el comando REINDEX , que solo est谩 destinado a reconstruir el 铆ndice, funciona solo en modo de bloqueo hasta la versi贸n 12 , lo que hace que sea imposible de usar. PostgreSQL 12 agrega soporte CONCURRENTEMENTE y ahora se puede usar.



REINDEX INDEX CONCURRENTLY my_table_index --  PG 12

      
      





Crear un 铆ndice en una tabla particionada



Tambi茅n deber铆amos discutir la creaci贸n de 铆ndices para tablas particionadas. En PostgreSQL, hay 2 tipos de particionamiento: por herencia y declarativo, que apareci贸 en la versi贸n 10. Veamos ambos con un ejemplo sencillo. 



Supongamos que queremos particionar una tabla por fecha y que cada partici贸n contendr谩 datos durante un a帽o.



Al particionar por herencia, tendremos aproximadamente el siguiente esquema.



Tabla de padres:



CREATE TABLE my_table (
    ...
    reg_date   date not null
)

      
      





Particiones secundarias para 2020 y 2021:



CREATE TABLE my_table_y2020 (
CHECK ( reg_date >= DATE '2020-01-01' AND reg_date < DATE '2021-01-01' ))
INHERITS (my_table);

CREATE TABLE my_table_y2021 (
CHECK ( reg_date >= DATE '2021-01-01' AND reg_date < DATE '2022-01-01' ))
INHERITS (my_table);

      
      





脥ndices por campo de partici贸n para cada una de las particiones:



CREATE INDEX ON my_table_y2020 (reg_date);
CREATE INDEX ON my_table_y2021 (reg_date);

      
      





Dejemos la creaci贸n de un disparador / regla para insertar datos en una tabla.



Lo m谩s importante aqu铆 es que cada una de las particiones es pr谩cticamente una tabla independiente que se mantiene por separado. Por lo tanto, la creaci贸n de nuevos 铆ndices tambi茅n se realiza como con las tablas regulares:



CREATE CONCURRENTLY INDEX my_table_y2020_index ON my_table_y2020 (name);
CREATE CONCURRENTLY INDEX my_table_y2021_index ON my_table_y2021 (name);

      
      





Ahora veamos el particionamiento declarativo.



CREATE TABLE my_table (...) PARTITION BY RANGE (reg_date);
CREATE TABLE my_table_y2020 PARTITION OF my_table FOR VALUES FROM ('2020-01-01') TO ('2020-12-31');
CREATE TABLE my_table_y2021 PARTITION OF my_table FOR VALUES FROM ('2021-01-01') TO ('2021-12-31');

      
      





La creaci贸n de 铆ndices depende de la versi贸n de PostgreSQL. En la versi贸n 10, los 铆ndices se crean por separado, al igual que en el enfoque anterior. En consecuencia, la creaci贸n de nuevos 铆ndices para una tabla existente tambi茅n se realiza de la misma manera.



En la versi贸n 11, se ha mejorado el particionamiento declarativo y las tablas ahora se sirven juntas . La creaci贸n de un 铆ndice en la tabla principal crea autom谩ticamente 铆ndices para todas las particiones nuevas y existentes que se crear谩n en el futuro:



--  PG 11    ()  
CREATE INDEX ON my_table (reg_date)

      
      





Esto es 煤til cuando se crea una tabla particionada, pero no cuando se crea un 铆ndice nuevo en una tabla existente, porque el comando toma un bloqueo fuerte mientras se crean los 铆ndices.



CREATE INDEX ON my_table (name) --  

      
      





Desafortunadamente, CREATE INDEX no admite la palabra clave CONCURRENTLY para tablas particionadas. Para sortear la limitaci贸n y migrar sin bloquear, puede hacer lo siguiente.



  1. Crear 铆ndice en la tabla principal con 脷NICA opci贸n

    CREATE INDEX my_table_index ON ONLY my_table (name)
          
          





    El comando crear谩 un 铆ndice no v谩lido vac铆o sin crear 铆ndices para las particiones .
  2. Cree 铆ndices para cada una de las particiones:

    CREATE CONCURRENTLY INDEX my_table_y2020_index ON my_table_y2020 (name);
    CREATE CONCURRENTLY INDEX my_table_y2021_index ON my_table_y2021 (name);
          
          



  3. Adjunte 铆ndices de particiones al 铆ndice de la tabla principal:

    ALTER INDEX my_table_index ATTACH PARTITION my_table_y2020_index;
    ALTER INDEX my_table_index ATTACH PARTITION my_table_y2021_index;
    
          
          



    Una vez que se hayan adjuntado todos los 铆ndices, el 铆ndice de la tabla principal se volver谩 v谩lido autom谩ticamente.


Limitaciones



Ahora repasemos las restricciones: claves NOT NULL, for谩neas, 煤nicas y primarias.



Creando una restricci贸n NOT NULL



ALTER TABLE my_table ALTER COLUMN name SET NOT NULL --  

      
      





Al crear una restricci贸n de esta manera, se escanear谩 toda la tabla; se verificar谩 en todas las filas la condici贸n no nula y, si la tabla es grande, puede llevar mucho tiempo. El bloque fuerte que captura este comando bloquear谩 todas las solicitudes simult谩neas hasta que se complete. 



驴Qu茅 se puede hacer? PostgreSQL tiene otro tipo de restricci贸n, CHECK , que se puede usar para obtener el resultado deseado. Esta restricci贸n prueba cualquier condici贸n booleana que consta de columnas de fila. En nuestro caso, la condici贸n es trivial - CHECK (name IS NOT NULL)



. Pero lo m谩s importante es que la restricci贸n CHECK admite la invalidaci贸n (palabra clave NOT VALID



):



ALTER TABLE my_table ADD CONSTRAINT chk_name_not_null 
    CHECK (name IS NOT NULL) NOT VALID -- ,  PG 9.2

      
      





La restricci贸n creada de esta manera se aplica solo a los registros reci茅n agregados y modificados, y los existentes no se verifican, por lo que la tabla no se escanea. 



Para garantizar que los registros existentes tambi茅n satisfagan la restricci贸n, es necesario validarlo (por supuesto, primero actualizando los datos en la tabla):



ALTER TABLE my_table VALIDATE CONSTRAINT chk_name_not_null 

      
      





El comando itera sobre las filas de la tabla y verifica que todos los registros no sean nulos. Pero a diferencia de la restricci贸n NOT NULL habitual, el bloqueo capturado en este comando no es tan fuerte (ShareUpdateExclusive); no bloquea las operaciones de inserci贸n, actualizaci贸n y eliminaci贸n. 



Creando una clave for谩nea



ALTER TABLE my_table ADD CONSTRAINT fk_group 
    FOREIGN KEY (group_id) REFERENCES groups(id) --   

      
      





Cuando se agrega una clave externa, todos los registros de la tabla secundaria se comprueban en busca de un valor en la principal. Si la mesa es grande, entonces este escaneo ser谩 largo y el bloqueo que se mantiene en ambas mesas tambi茅n ser谩 largo. 



Afortunadamente, las claves for谩neas en PostgreSQL tambi茅n admiten NOT VALID, lo que significa que podemos usar el mismo enfoque que se discuti贸 anteriormente con CHECK. Creemos una clave externa no v谩lida: 



ALTER TABLE my_table ADD CONSTRAINT fk_group 
    FOREIGN KEY (group_id) REFERENCES groups(id) NOT VALID

      
      





luego actualizamos los datos y realizamos la validaci贸n:



ALTER TABLE my_table VALIDATE CONSTRAINT fk_group_id

      
      







Crea una restricci贸n 煤nica



ALTER TABLE my_table ADD CONSTRAINT uk_my_table UNIQUE (id) --  

      
      





Como en el caso de las restricciones discutidas anteriormente, el comando captura un bloqueo estricto, bajo el cual verifica todas las filas de la tabla con la restricci贸n, en este caso, la unicidad. 



Es importante saber que, bajo el cap贸, PostgreSQL impone restricciones 煤nicas utilizando 铆ndices 煤nicos. En otras palabras, cuando se crea una restricci贸n, se crea un 铆ndice 煤nico correspondiente con el mismo nombre para atender esa restricci贸n. Con la siguiente consulta, puede averiguar el 铆ndice de publicaci贸n de la restricci贸n:



SELECT conindid index_oid, conindid::regclass index_name 
    FROM pg_constraint 
WHERE conname = 'uk_my_table_id'

      
      





Al mismo tiempo, se utiliza en la mayor铆a de las limitaciones de tiempo de creaci贸n, lo mismo ocurre con el 铆ndice y su posterior enlace para limitar muy r谩pidamente. Adem谩s, si ya ha creado un 铆ndice 煤nico, puede hacerlo usted mismo creando un 铆ndice utilizando las palabras clave USING INDEX:



ALTER TABLE my_table ADD CONSTRAINT uk_my_table_id UNIQUE 
    USING INDEX uk_my_table_id -- ,  PG 9.1

      
      





Por lo tanto, la idea es simple: creamos un 铆ndice 煤nico CONCURRENTEMENTE, como discutimos anteriormente, y luego creamos una restricci贸n 煤nica basada en 茅l.



En este punto, puede surgir la pregunta : 驴por qu茅 crear una restricci贸n, si el 铆ndice hace exactamente lo que se requiere, garantiza la unicidad de los valores? Si excluimos los 铆ndices parciales de la comparaci贸n , entonces, desde un punto de vista funcional, el resultado es realmente casi id茅ntico. La 煤nica diferencia que hemos encontrado es que las restricciones pueden diferirse , pero los 铆ndices no. La documentaci贸n para versiones anteriores de PostgreSQL (hasta la 9.4 incluida) ten铆a una nota al piecon la informaci贸n de que la forma preferida de crear una restricci贸n de unicidad es crear expl铆citamente una restricci贸n ALTER TABLE ... ADD CONSTRAINT



, y el uso de 铆ndices debe considerarse un detalle de implementaci贸n. Sin embargo, en versiones m谩s recientes se ha eliminado esta nota a pie de p谩gina.



Creando una clave primaria



Adem谩s de ser 煤nica, la clave principal impone la restricci贸n no nula. Si la columna originalmente ten铆a tal restricci贸n, entonces no ser谩 dif铆cil "convertirla" en una clave primaria - tambi茅n creamos un 铆ndice 煤nico CONCURRENTEMENTE, y luego la clave primaria:



ALTER TABLE my_table ADD CONSTRAINT uk_my_table_id PRIMARY KEY 
    USING INDEX uk_my_table_id --  id is NOT NULL

      
      





Es importante tener en cuenta que la columna debe tener una restricci贸n NO NULL "justa"; el enfoque CHECK discutido anteriormente no funcionar谩.



Si no hay l铆mite, hasta la und茅cima versi贸n de PostgreSQL no hay nada que hacer; no hay forma de crear una clave primaria sin bloquear.



Si tiene PostgreSQL 11 o m谩s reciente, esto se puede lograr creando una nueva columna que reemplazar谩 a la existente. Entonces, paso a paso.



Cree una nueva columna que no sea nula de forma predeterminada y tenga un valor predeterminado:



ALTER TABLE my_table ADD COLUMN new_id INTEGER NOT NULL DEFAULT -1 --   PG 11

      
      





Configuramos la sincronizaci贸n de los datos de las columnas antiguas y nuevas usando un disparador:



CREATE FUNCTION on_insert_or_update() RETURNS TRIGGER AS
$$
BEGIN
    NEW.new_id = NEW.id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg BEFORE INSERT OR UPDATE ON my_table
FOR EACH ROW EXECUTE PROCEDURE on_insert_or_update();

      
      





A continuaci贸n, debe actualizar los datos de las filas que no se vieron afectadas por el activador:



UPDATE my_table SET new_id = id WHERE new_id = -1 --     

      
      





La solicitud con la actualizaci贸n anterior est谩 escrita "en la frente", en una mesa grande no vale la pena hacer esto, porque habr谩 un bloqueo largo. Como se mencion贸 anteriormente, el segundo art铆culo analizar谩 los enfoques para actualizar tablas grandes. Por ahora, supongamos que los datos est谩n actualizados y todo lo que queda es intercambiar las columnas.



ALTER TABLE my_table RENAME COLUMN id TO old_id;
ALTER TABLE my_table RENAME COLUMN new_id TO id;
ALTER TABLE my_table RENAME COLUMN old_id TO new_id;

      
      





En PostgreSQL, los comandos DDL son transaccionales; esto significa que puede cambiar el nombre, agregar, eliminar columnas y, al mismo tiempo, una transacci贸n paralela no ver谩 esto en el curso de sus operaciones.



Despu茅s de cambiar las columnas, queda crear un 铆ndice y "limpiar": eliminar el activador, la funci贸n y la columna anterior.



Una hoja de trucos r谩pida con migraciones



Antes de cualquier comando que capture bloqueos fuertes (casi todos ALTER TABLE ...



), se recomienda llamar a:



SET LOCAL lock_timeout TO '100ms'

      
      





Migraci贸n Enfoque recomendado
Agregar una columna
ALTER TABLE my_table ADD COLUMN new_column INTEGER

      
      



Agregar una columna con un valor predeterminado [y NO NULO] con PostgreSQL 11:

ALTER TABLE my_table ADD COLUMN new_column INTEGER DEFAULT 42 [NOT NULL]
      
      





antes de PostgreSQL 11:

  1. ALTER TABLE my_table ADD COLUMN new_column INTEGER;
    ALTER TABLE my_table ALTER COLUMN new_column SET DEFAULT 42;
          
          



  2. tabla de actualizaci贸n
Eliminar una columna
  1. eliminando restricciones (NOT NULL, CHECK, etc.)
  2. preparando el c贸digo
  3. ALTER TABLE my_table DROP COLUMN removed_column
    
          
          



Creaci贸n de 铆ndice
CREATE CONCURRENTLY INDEX my_table_index ON my_table (name)

      
      





Si falla:

  1. DROP INDEX CONCURRENTLY my_table_index
    
          
          



  2. tabla de actualizaci贸n
  3. CREATE CONCURRENTLY INDEX my_table_index ON my_table (name)
    
          
          





Crear un 铆ndice en una tabla particionada Particionamiento por herencia + declarativo en PG 10:

CREATE CONCURRENTLY INDEX my_table_y2020_index ON my_table_y2020 (name);
CREATE CONCURRENTLY INDEX my_table_y2021_index ON my_table_y2021 (name);
...

      
      





Particionamiento declarativo con PG 11:

  1. CREATE INDEX my_table_index ON ONLY my_table (name)
    
          
          



  2. CREATE CONCURRENTLY INDEX my_table_y2020_index ON my_table_y2020 (name);
    CREATE CONCURRENTLY INDEX my_table_y2021_index ON my_table_y2021 (name);
    ...
    
          
          



  3. ALTER INDEX my_table_index ATTACH PARTITION my_table_y2020_index;
    ALTER INDEX my_table_index ATTACH PARTITION my_table_y2021_index;
    ...
    
          
          



Creando una restricci贸n NOT NULL
  1. ALTER TABLE my_table ADD CONSTRAINT chk_name_not_null CHECK (name IS NOT NULL) NOT VALID
    
          
          



  2. tabla de actualizaci贸n
  3. ALTER TABLE my_table VALIDATE CONSTRAINT chk_name_not_null
    
          
          





Creando una clave for谩nea
  1. ALTER TABLE my_table ADD CONSTRAINT fk_group FOREIGN KEY (group_id)  REFERENCES groups(id) NOT VALID
    
          
          



  2. tabla de actualizaci贸n
  3. ALTER TABLE my_table VALIDATE CONSTRAINT fk_group_id
    
          
          



Crea una restricci贸n 煤nica
  1. CREATE UNIQUE INDEX CONCURRENTLY uk_my_table_id ON my_table(id)
    
          
          



  2. ALTER TABLE my_table ADD CONSTRAINT uk_my_table_id UNIQUE USING INDEX uk_my_table_id
    
          
          



Creando una clave primaria Si la columna NO ES NULA:

  1. CREATE UNIQUE INDEX CONCURRENTLY uk_my_table_id ON my_table(id)
    
          
          



  2. ALTER TABLE my_table ADD CONSTRAINT uk_my_table_id PRIMARY KEY USING INDEX uk_my_table_id
    
          
          





Si la columna ES NULA con PG 11:

  1. ALTER TABLE my_table ADD COLUMN new_id INTEGER NOT NULL DEFAULT -1
    
          
          



  2. CREATE FUNCTION on_insert_or_update() RETURNS TRIGGER AS
    $$
    BEGIN
        NEW.new_id = NEW.id;
        RETURN NEW;
    END;
    $$ LANGUAGE plpgsql;
    CREATE TRIGGER trg BEFORE INSERT OR UPDATE ON my_table
    FOR EACH ROW EXECUTE PROCEDURE on_insert_or_update();
    
          
          



  3. tabla de actualizaci贸n
  4. ALTER TABLE my_table RENAME COLUMN id TO old_id;
    ALTER TABLE my_table RENAME COLUMN new_id TO id;
    ALTER TABLE my_table RENAME COLUMN old_id TO new_id;
    
          
          



  5. CREATE UNIQUE INDEX CONCURRENTLY uk_my_table_id ON my_table(id)
    
          
          



  6. ALTER TABLE my_table ADD CONSTRAINT uk_my_table_id PRIMARY KEY USING INDEX uk_my_table_id
    
          
          



  7. DROP TRIGGER trg ON my_table;
    DROP FUNCTION on_insert_or_update();
    ALTER TABLE my_table DROP COLUMN new_id;
    
          
          





En el pr贸ximo art铆culo, veremos enfoques para actualizar tablas grandes.

隆Migraciones f谩ciles para todos!



All Articles