Hay una serie de índices aparte de los convencionales, que son poco conocidos, pero no por ello dejan de ser muy útiles.
GIN - índices optimizados para la búsqueda de subelementos, dentro de una columna, por ejemplo columnas de tipo Array, columnas que almacenen JSON o campos de texto para hacer búsquedas FTS (Full Text Search).
GIST - índices para búsquedas de datos geolocalizados.
BRIN - índices que ahorra espacio de almacenamiento para aquellos datos que se encuentran ordenados de forma natural. Por ejemplo las entradas de un fichero log, siempre se generan de forma secuencial en base al campo de fecha de la entrada.
HASH - indice optimizado en velocidad para la búsqueda de elementos por igualdad. Por ejemplo la dirección de email de usuarios. Usando este índice tendríamos máxima velocidad de acceso para buscar a un usuario por su email.
Intentando arrojar un poco de luz a problemas que puedes encontrarte. Especialmente con PostgreSql
Mostrando entradas con la etiqueta postgresql. Mostrar todas las entradas
Mostrando entradas con la etiqueta postgresql. Mostrar todas las entradas
lunes, 28 de mayo de 2018
jueves, 19 de febrero de 2015
analizando consultas en Postgresql. Parte I
Tenemos una potente herramienta para analizar las consultas en PostgreSQL: explain analyze
explain analyze select * from usuario; Seq Scan on usuario (cost=0.00..4743.71 rows=44471 width=243) (actual time=0.026..59.431 rows=44474 loops=1) Total runtime: 63.645 ms (2 filas)
Seq Scan, significa que la consulta se hará fila a fila secuencialmente sin acceso por índice.
El valor numérico cost=0,00, es el tiempo que tiene que esperar la consulta en ser ejecutada. En este caso el tiempo es 0.00 porque puede ejecutarse inmediatamente.
El segundo valor 4743,71, es el numero de fetches necesarios para recuperar todas las filas de la consulta, el número de fetches lo podemos minimizar por ejemplo aplicando LIMIT.
Rows=44.471 es el número de filas que devolverá la consulta y width el tamaño medio en bytes de cada fila.
Truco: si vas a realizar análisis de operaciones de escritura, encierralas en un bloque de transacción, para poder ejecutarlas las veces que necesites y que no se produzcan cambios en la base de datos:
begin; explain analyze insert ...; rollback;
Etiquetas:
optimización,
postgres,
postgresql,
tuning
lunes, 15 de diciembre de 2014
Usuario de solo lectura
A veces puedes necesitar dar acceso a tu base de datos a una herramienta de terceros, de la cual no te fias o solo quieres que haga consultas pero no toque nada. Una forma rápida es crear un usuario de solo lectura:
create user 'readonly' password 'laquesea';
alter user readonly set default_transaction_read_only = on;
Luego puedes darle permiso sobre todas las tablas de los esquemas que necesites:
select 'grant select on ' || a.schemaname || '.' || a.tablename || ' to readonly;' from pg_tables a where schemaname in ('esquema1');
select 'grant select on ' || a.schemaname || '.' || a.tablename || ' to readonly;' from pg_tables a where schemaname in ('esquema2');
Y por último dale permisos de acceso a los esquemas:
grant usage on schema cromos to readonly;
grant usage on schema coleccion to readonly;
create user 'readonly' password 'laquesea';
alter user readonly set default_transaction_read_only = on;
Luego puedes darle permiso sobre todas las tablas de los esquemas que necesites:
select 'grant select on ' || a.schemaname || '.' || a.tablename || ' to readonly;' from pg_tables a where schemaname in ('esquema1');
select 'grant select on ' || a.schemaname || '.' || a.tablename || ' to readonly;' from pg_tables a where schemaname in ('esquema2');
Y por último dale permisos de acceso a los esquemas:
grant usage on schema cromos to readonly;
grant usage on schema coleccion to readonly;
lunes, 6 de octubre de 2014
Bloque anónimo
A veces necesitas hacer uso de las estructuras de plpgsql, por ejemplo, en scripts .sql que llamas desde tareas del cron, etc. Para ello tenemos los bloques DO.
A continuación un bloque sencillo, que comprueba si es primeros de mes y añade un mes de antigüedad a los usuarios registrados. Como puedes ver podemos utilizar variables y ejecutar comandos de los cuales no necesito conocer el resultado (perform).
do $$
declare
dia integer;
begin
select extract ('day' from current_date) into dia from editorial;
if dia = 1 then
perform 'update usuario set months=months+1';
else
raise notice 'Hoy no es día 1';
end if;
end$$;
A continuación un bloque sencillo, que comprueba si es primeros de mes y añade un mes de antigüedad a los usuarios registrados. Como puedes ver podemos utilizar variables y ejecutar comandos de los cuales no necesito conocer el resultado (perform).
do $$
declare
dia integer;
begin
select extract ('day' from current_date) into dia from editorial;
if dia = 1 then
perform 'update usuario set months=months+1';
else
raise notice 'Hoy no es día 1';
end if;
end$$;
martes, 12 de agosto de 2014
Cambiar atributos default o not null a una columna existente
Si quieres quitar la propiedad default o not null a una columna existente;
alter table alter column drop not null;
alter table alter column drop default;
Por el contrario, si quieres añadir el atributo not null o la propiedad default:
alter table alter column set not null;
alter table alter column set default current_timestamp;
alter table
alter table
Por el contrario, si quieres añadir el atributo not null o la propiedad default:
alter table
alter table
Ultimo id insertado
Cuando queremos recuperar el último id insertado en una tabla, para por ejemplo asignarlo en los registros de sus hijas. Lo habitual es consultar el último valor de la secuencia asociada al campo clave. Por ejemplo:
select currval('cambio_id_seq');
En este caso tenemos que conocer el nombre de la secuencia, si lo desconociésemos podríamos utilizar la función pg_get_serial_sequence que nos proporcionaría el nombre de la secuencia asociado a un campo determinado de una tabla:
select currval(pg_get_serial_squence('cambio','id'));
Esta opción tiene un problema. Solo nos funcionará si nadie más utiliza dicha secuencia en otra sesión. Es decir en entorno de multiples conexiones no es recomendable.
Por el contrario LASTVAL(), está aislado a nivel de sesión, por lo que no tiene dichos efectos indeseados
Pero si la tabla tuviese un trigger que insertará nuevos registros o modificara la secuencia por algún motivo, LASTVAL() no funcionaría correctamente.
Por todo lo anterior, el mejor método posible es utilizar la clausula RETURNING después de la inserción. Por ejemplo:
insert into cambio (id,usuario1,usuario2)
values (DEFAULT,usuario1_id,usuario2_id)
RETURNING id into cambio_id;
En la variable cambio_id tendremos almacenado seguro el valor de la inserción que estamos haciendo en nuestra sesión, sin efecto colateral alguno.
select currval('cambio_id_seq');
En este caso tenemos que conocer el nombre de la secuencia, si lo desconociésemos podríamos utilizar la función pg_get_serial_sequence que nos proporcionaría el nombre de la secuencia asociado a un campo determinado de una tabla:
select currval(pg_get_serial_squence('cambio','id'));
Esta opción tiene un problema. Solo nos funcionará si nadie más utiliza dicha secuencia en otra sesión. Es decir en entorno de multiples conexiones no es recomendable.
Por el contrario LASTVAL(), está aislado a nivel de sesión, por lo que no tiene dichos efectos indeseados
Pero si la tabla tuviese un trigger que insertará nuevos registros o modificara la secuencia por algún motivo, LASTVAL() no funcionaría correctamente.
Por todo lo anterior, el mejor método posible es utilizar la clausula RETURNING después de la inserción. Por ejemplo:
insert into cambio (id,usuario1,usuario2)
values (DEFAULT,usuario1_id,usuario2_id)
RETURNING id into cambio_id;
En la variable cambio_id tendremos almacenado seguro el valor de la inserción que estamos haciendo en nuestra sesión, sin efecto colateral alguno.
martes, 27 de mayo de 2014
Script migración tablas sueltas
Script de apoyo para mover el contenido una serie de tablas de una base de datos a otra.
Toma el contenido de las tablas tabla1..tabla4 y lo vuelca en /tmp/fichero.dump sin tomar la estructura de la tabla.
Utiliza unas tablas auxiliares para guardar el contenido de las tablas que tienen relaciones con tabla1..tabla4.
Vacía las tablas tabla2_related1..tabla2_related3 con relaciones sobre las tablas que estamos moviendo.
Posteriormente vacía las tablas destino tabla1..tabla4 y despues las rellena con el contenido del fichero /tmp/fichero.dump
Por último restaura el contenido de las tablas con relaciones, utilizando la cópia almacenada en las tablas auxiliares.
Espero que os pueda servir de ayuda en alguna ocasión.
#Volcado de las tablas originales deseadas (primero las maestras luego las dependientes)
pg_dump -U -h -p --inserts --data-only -t tabla1 > /tmp/fichero.dump
pg_dump -U -h -p --inserts --data-only -t tabla2 >> /tmp/fichero.dump
pg_dump -U -h -p --inserts --data-only -t tabla3 >> /tmp/fichero.dump
pg_dump -U -h -p --inserts --data-only -t tabla4 >> /tmp/fichero.dump
#Borrado de las auxiliares
psql -h -U -p -c "drop table schema.aux_tabla2_related1"
psql -h -U -p -c "drop table schema.aux_tabla2_related2"
psql -h -U -p -c "drop table schema.aux_tabla2_related3"
#Relleno de las auxiliares
psql -h -U -p -c "create table schema.aux_tabla2_related1 as select * from schema.tabla2_related1"
psql -h -U -p -c "create table schema.aux_tabla2_related2 as select * from schema.tabla2_related2"
psql -h -U -p -c "create table schema.aux_tabla2_related3 as select * from schema.tabla2_related3"
#Borrado de las relacionadas
psql -h -U -p -c "delete from schema.tabla2_related1"
psql -h -U -p -c "delete from schema.tabla2_related2"
psql -h -U -p -c "delete from schema.tabla2_related3"
#Borramos el contenido de las tablas deseadas
psql -h -U -p -c "delete from schema.dictionary"
psql -h -U -p -c "delete from schema.tabla4"
psql -h -U -p -c "delete from schema.tabla2"
psql -h -U -p -c "delete from schema.tabla1"
#Rellenamos las tablas deseadas
psql -h -U -p < /tmp/fichero.dump
#Restauramos las relacionadas
psql -h -U -p -c "insert into schema.tabla2_related1 select * from schema.aux_tabla2_related1"
psql -h -U -p -c "insert into schema.tabla2_related2 select * from schema.aux_tabla2_related2"
psql -h -U -p -c "insert into schema.tabla2_related3 select * from schema.aux_tabla2_related3"
Toma el contenido de las tablas tabla1..tabla4 y lo vuelca en /tmp/fichero.dump sin tomar la estructura de la tabla.
Utiliza unas tablas auxiliares para guardar el contenido de las tablas que tienen relaciones con tabla1..tabla4.
Vacía las tablas tabla2_related1..tabla2_related3 con relaciones sobre las tablas que estamos moviendo.
Posteriormente vacía las tablas destino tabla1..tabla4 y despues las rellena con el contenido del fichero /tmp/fichero.dump
Por último restaura el contenido de las tablas con relaciones, utilizando la cópia almacenada en las tablas auxiliares.
Espero que os pueda servir de ayuda en alguna ocasión.
#Volcado de las tablas originales deseadas (primero las maestras luego las dependientes)
pg_dump -U
pg_dump -U
pg_dump -U
pg_dump -U
#Borrado de las auxiliares
psql -h
psql -h
psql -h
#Relleno de las auxiliares
psql -h
psql -h
psql -h
#Borrado de las relacionadas
psql -h
psql -h
psql -h
#Borramos el contenido de las tablas deseadas
psql -h
psql -h
psql -h
psql -h
#Rellenamos las tablas deseadas
psql -h
#Restauramos las relacionadas
psql -h
psql -h
psql -h
lunes, 17 de marzo de 2014
Busqueda de Esquemas en Postgres por defecto
Si teneis una base de datos con varios esquemas, llega un momento en que es complicado encontrar una tabla y es farragoso tener que escribir el nombre del esquema delante de cada tabla para acceder al contenido.
Una solución es modificar el search_path del usuario con el que te conectas a la base de datos, por ejemplo del usuario user_connect, conectado como superusuario:
alter user "user_connect" set search_path to "$user",public,master,soccer;
Pero es mucho mejor indicar el camino de acceso a nivel de base de datos, en lugar de hacerlo a nivel de usuario.
alter database "league" set search_path to "$user",public,master,soccer;
De esta forma todos los usuarios que se conecten a esta base de datos, podrán encontrar las tablas de forma cómoda, en los esquemas public, master, soccer y en un esquema con su propio nombre de usuario con el que se ha conectado.
Una solución es modificar el search_path del usuario con el que te conectas a la base de datos, por ejemplo del usuario user_connect, conectado como superusuario:
alter user "user_connect" set search_path to "$user",public,master,soccer;
Pero es mucho mejor indicar el camino de acceso a nivel de base de datos, en lugar de hacerlo a nivel de usuario.
alter database "league" set search_path to "$user",public,master,soccer;
De esta forma todos los usuarios que se conecten a esta base de datos, podrán encontrar las tablas de forma cómoda, en los esquemas public, master, soccer y en un esquema con su propio nombre de usuario con el que se ha conectado.
domingo, 12 de enero de 2014
Bloque de comandos sin crear una función
Si necesitas hacer pruebas con un bloque de comandos de pgsql, sin tener que crear una función, esta es la forma:
do
$$declare
intervalo varchar;
fecha timestamp;
begin
--fecha := ;
intervalo:= 1 || ' days';
fecha := current_timestamp + intervalo::interval;
insert into acumulado(usuario_id,f_fin,texto) values ( 1, fecha, 'texto' );
end$$;
do
$$declare
intervalo varchar;
fecha timestamp;
begin
--fecha := ;
intervalo:= 1 || ' days';
fecha := current_timestamp + intervalo::interval;
insert into acumulado(usuario_id,f_fin,texto) values ( 1, fecha, 'texto' );
end$$;
lunes, 30 de septiembre de 2013
Consulta de Bloqueos en PostgreSql > 9.2 y como matar procesos
Para las versiones de Postgres 9.2 o superiores podéis crear una vista que facilitará la consulta de los procesos que actualmente tiene bloqueos en vuestro sistema:
CREATE OR REPLACE VIEW public.procesos_bloqueantes(
blocking_pid,
blocking_user,
blocking_query,
blocked_pid,
blocked_user,
blocked_query,
age)
AS
SELECT kl.pid AS blocking_pid,
ka.usename AS blocking_user,
ka.query AS blocking_query,
bl.pid AS blocked_pid,
a.usename AS blocked_user,
a.query AS blocked_query,
to_char(age(now(), a.query_start), 'HH24h:MIm:SSs' ::text) AS age
FROM pg_locks bl
JOIN pg_stat_activity a ON bl.pid = a.pid
JOIN pg_locks kl ON bl.locktype = kl.locktype AND NOT bl.database IS
DISTINCT
FROM kl.database AND NOT bl.relation IS DISTINCT
FROM kl.relation AND NOT bl.page IS DISTINCT
FROM kl.page AND NOT bl.tuple IS DISTINCT
FROM kl.tuple AND NOT bl.virtualxid IS DISTINCT
FROM kl.virtualxid AND NOT bl.transactionid IS DISTINCT
FROM kl.transactionid AND NOT bl.classid IS DISTINCT
FROM kl.classid AND NOT bl.objid IS DISTINCT
FROM kl.objid AND NOT bl.objsubid IS DISTINCT
FROM kl.objsubid AND bl.pid <> kl.pid
JOIN pg_stat_activity ka ON kl.pid = ka.pid
WHERE kl.granted AND
NOT bl.granted
ORDER BY a.query_start;
Una vez localizados los procesos que están produciendo el bloqueo y que sentencia están ejecutando, podemos tomar la decisión de eliminarlos si llevan demasiado tiempo en ejecución y no nos importa perder él resultado de la acción que estaban ejecutando. El identificador de proceso a matar es el "blocking_id" de la vista:
select pg_cancel_backend ( blocking_pid );
CREATE OR REPLACE VIEW public.procesos_bloqueantes(
blocking_pid,
blocking_user,
blocking_query,
blocked_pid,
blocked_user,
blocked_query,
age)
AS
SELECT kl.pid AS blocking_pid,
ka.usename AS blocking_user,
ka.query AS blocking_query,
bl.pid AS blocked_pid,
a.usename AS blocked_user,
a.query AS blocked_query,
to_char(age(now(), a.query_start), 'HH24h:MIm:SSs' ::text) AS age
FROM pg_locks bl
JOIN pg_stat_activity a ON bl.pid = a.pid
JOIN pg_locks kl ON bl.locktype = kl.locktype AND NOT bl.database IS
DISTINCT
FROM kl.database AND NOT bl.relation IS DISTINCT
FROM kl.relation AND NOT bl.page IS DISTINCT
FROM kl.page AND NOT bl.tuple IS DISTINCT
FROM kl.tuple AND NOT bl.virtualxid IS DISTINCT
FROM kl.virtualxid AND NOT bl.transactionid IS DISTINCT
FROM kl.transactionid AND NOT bl.classid IS DISTINCT
FROM kl.classid AND NOT bl.objid IS DISTINCT
FROM kl.objid AND NOT bl.objsubid IS DISTINCT
FROM kl.objsubid AND bl.pid <> kl.pid
JOIN pg_stat_activity ka ON kl.pid = ka.pid
WHERE kl.granted AND
NOT bl.granted
ORDER BY a.query_start;
Una vez localizados los procesos que están produciendo el bloqueo y que sentencia están ejecutando, podemos tomar la decisión de eliminarlos si llevan demasiado tiempo en ejecución y no nos importa perder él resultado de la acción que estaban ejecutando. El identificador de proceso a matar es el "blocking_id" de la vista:
select pg_cancel_backend ( blocking_pid );
Etiquetas:
administración,
monitorización,
postgresql
jueves, 26 de septiembre de 2013
Indices no utilizados en postgresql
Consultar los índices de tu bbdd que no están siendo utilizados, para ahorrar espacio o replantearte sus campos:
SELECT
schemaname || '.' || relname AS table,
indexrelname AS index,
pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
idx_scan as index_scans
FROM pg_stat_user_indexes ui
JOIN pg_index i ON ui.indexrelid = i.indexrelid
WHERE NOT indisunique AND idx_scan < 50 AND pg_relation_size(relid) > 5 * 8192
ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
pg_relation_size(i.indexrelid) DESC;
Etiquetas:
optimización,
postgres,
postgresql,
tuning
viernes, 10 de mayo de 2013
Consulta de Bloqueos en PostgresSql
Si tienes problemas de registros bloqueados en la base de datos ejecuta la siguiente sentencia para visualizar que sentencia/s lo están provocando:
SELECT bl.pid AS blocked_pid, a.usename AS blocked_user,
kl.pid AS blocking_pid, ka.usename AS blocking_user, a.query AS blocked_statement
FROM pg_catalog.pg_locks bl
JOIN pg_catalog.pg_stat_activity a
ON bl.pid = a.pid
JOIN pg_catalog.pg_locks kl
JOIN pg_catalog.pg_stat_activity ka
ON kl.pid = ka.pid
ON bl.transactionid = kl.transactionid AND bl.pid != kl.pid
WHERE NOT bl.granted;
SELECT bl.pid AS blocked_pid, a.usename AS blocked_user,
kl.pid AS blocking_pid, ka.usename AS blocking_user, a.query AS blocked_statement
FROM pg_catalog.pg_locks bl
JOIN pg_catalog.pg_stat_activity a
ON bl.pid = a.pid
JOIN pg_catalog.pg_locks kl
JOIN pg_catalog.pg_stat_activity ka
ON kl.pid = ka.pid
ON bl.transactionid = kl.transactionid AND bl.pid != kl.pid
WHERE NOT bl.granted;
Suscribirse a:
Entradas (Atom)