<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Bases de datos on inetshell</title><link>https://inet.sh/es/posts/databases/</link><description>Recent content in Bases de datos on inetshell</description><generator>Hugo</generator><language>es</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/es/posts/databases/index.xml" rel="self" type="application/rss+xml"/><item><title>Oracle CQN con Python: enterarte de los cambios de una tabla sin polling (y por qué oracle_fdw no dispara triggers)</title><link>https://inet.sh/es/posts/databases/oracle-cqn-python-sin-polling/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/databases/oracle-cqn-python-sin-polling/</guid><description>Continuous Query Notification hace que Oracle avise a tu aplicación cuando cambian las filas de una consulta, sin polling: GRANT CHANGE NOTIFICATION, conexión con events=True, subscribe() y registerquery() en python-oracledb. Qué exige de red (la base conecta de vuelta al cliente, o client_initiated=True), el ORA-29977 del SELECT *, y el caso real: sincronizar Oracle hacia PostgreSQL, porque oracle_fdw no dispara triggers cuando cambia el lado Oracle.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">GRANT</span><span class="w"> </span><span class="n">CHANGE</span><span class="w"> </span><span class="n">NOTIFICATION</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="n">app_user</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">oracledb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">on_change</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">query</span> <span class="ow">in</span> <span class="n">message</span><span class="o">.</span><span class="n">queries</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">table</span> <span class="ow">in</span> <span class="n">query</span><span class="o">.</span><span class="n">tables</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">table</span><span class="o">.</span><span class="n">rows</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="n">table</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">row</span><span class="o">.</span><span class="n">operation</span><span class="p">,</span> <span class="n">row</span><span class="o">.</span><span class="n">rowid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">conn</span> <span class="o">=</span> <span class="n">oracledb</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">user</span><span class="o">=</span><span class="s2">&#34;app_user&#34;</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="n">pw</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">dsn</span><span class="o">=</span><span class="s2">&#34;db.example:1521/orclpdb1&#34;</span><span class="p">,</span> <span class="n">events</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">sub</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">subscribe</span><span class="p">(</span><span class="n">callback</span><span class="o">=</span><span class="n">on_change</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                     <span class="n">operations</span><span class="o">=</span><span class="n">oracledb</span><span class="o">.</span><span class="n">OPCODE_INSERT</span> <span class="o">|</span> <span class="n">oracledb</span><span class="o">.</span><span class="n">OPCODE_UPDATE</span> <span class="o">|</span> <span class="n">oracledb</span><span class="o">.</span><span class="n">OPCODE_DELETE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                     <span class="n">qos</span><span class="o">=</span><span class="n">oracledb</span><span class="o">.</span><span class="n">SUBSCR_QOS_QUERY</span> <span class="o">|</span> <span class="n">oracledb</span><span class="o">.</span><span class="n">SUBSCR_QOS_ROWIDS</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">sub</span><span class="o">.</span><span class="n">registerquery</span><span class="p">(</span><span class="s2">&#34;select id, status, updated_at from orders&#34;</span><span class="p">)</span>   <span class="c1"># columnas explícitas, nunca SELECT *</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">input</span><span class="p">(</span><span class="s2">&#34;Ctrl+C para salir</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Cada <code>COMMIT</code> que cambie el resultado de esa consulta dispara <code>on_change</code> con los <code>rowid</code> afectados. Sin polling, sin triggers, sin GoldenGate.</p>
<h2 id="por-qué">Por qué</h2>
<p>Cuando otro sistema necesita reaccionar a cambios en Oracle (invalidar una caché, replicar hacia otra base, disparar un proceso), lo habitual es preguntar cada N segundos con <code>WHERE updated_at &gt; :last</code>. Funciona hasta que no: filas sin columna de fecha, borrados que no dejan rastro, carga inútil sobre la base.</p>
<p><strong>Continuous Query Notification</strong> invierte el flujo: registras una consulta y Oracle te llama cuando cambia. En modo <em>query</em> (<code>SUBSCR_QOS_QUERY</code>) avisa solo si cambia el resultado de esa consulta; en modo <em>object</em> (sin ese flag) avisa ante cualquier cambio en las tablas implicadas. <code>SUBSCR_QOS_ROWIDS</code> incluye los <code>rowid</code> en la notificación, con lo que el callback puede leer exactamente esas filas.</p>
<h2 id="el-caso-replicar-oracle-a-postgresql">El caso: replicar Oracle a PostgreSQL</h2>
<p><code>oracle_fdw</code> deja consultar tablas de Oracle desde PostgreSQL como si fueran locales, y es tentador pensar que un trigger sobre la <em>foreign table</em> se disparará cuando cambien los datos en Oracle. No: los triggers de una foreign table solo saltan con escrituras hechas <strong>desde PostgreSQL</strong> a través de ella. El autor de <code>oracle_fdw</code> lo dice tal cual: no hay forma automática de mantener sincronizada una tabla de PostgreSQL con una de Oracle desde el lado PostgreSQL; eso tiene que dispararlo Oracle.</p>
<p>Con CQN el pequeño servicio en Python es la pieza que falta:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">on_change</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">rowids</span> <span class="o">=</span> <span class="p">[</span><span class="n">row</span><span class="o">.</span><span class="n">rowid</span> <span class="k">for</span> <span class="n">q</span> <span class="ow">in</span> <span class="n">message</span><span class="o">.</span><span class="n">queries</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">q</span><span class="o">.</span><span class="n">tables</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">t</span><span class="o">.</span><span class="n">rows</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span> <span class="k">as</span> <span class="n">cur</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">cur</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;select id, status, updated_at from orders where rowid in (</span><span class="si">%s</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="o">%</span> <span class="s2">&#34;,&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&#39;</span><span class="si">{</span><span class="n">r</span><span class="si">}</span><span class="s2">&#39;&#34;</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">rowids</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">id_</span><span class="p">,</span> <span class="n">status</span><span class="p">,</span> <span class="n">updated_at</span> <span class="ow">in</span> <span class="n">cur</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">pg</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;insert into orders (id, status, updated_at) values (</span><span class="si">%s</span><span class="s2">, </span><span class="si">%s</span><span class="s2">, </span><span class="si">%s</span><span class="s2">) &#34;</span>
</span></span><span class="line"><span class="cl">                       <span class="s2">&#34;on conflict (id) do update set status = excluded.status, updated_at = excluded.updated_at&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                       <span class="p">(</span><span class="n">id_</span><span class="p">,</span> <span class="n">status</span><span class="p">,</span> <span class="n">updated_at</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">pg</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Los borrados llegan con <code>row.operation &amp; oracledb.OPCODE_DELETE</code> y un <code>rowid</code> que ya no existe: borra en PostgreSQL por la clave que guardaste asociada a ese <code>rowid</code>, o marca la fila.</p>
<h2 id="requisitos">Requisitos</h2>
<ul>
<li><strong>Permiso</strong>: <code>GRANT CHANGE NOTIFICATION TO app_user</code>, además de <code>SELECT</code> sobre las tablas.</li>
<li><strong><code>events=True</code></strong> en la conexión que se suscribe; sin eso <code>subscribe()</code> falla.</li>
<li><strong>Red de vuelta.</strong> Por defecto es <strong>la base la que abre una conexión hacia tu aplicación</strong> para entregar las notificaciones: la máquina del cliente necesita una IP alcanzable desde el servidor Oracle y un puerto abierto (<code>subscribe(..., ip_address=…, port=…)</code> fija cuáles). Detrás de NAT, en un contenedor o con firewall entre medias, las notificaciones simplemente no llegan y nadie avisa.</li>
<li><strong><code>client_initiated=True</code></strong> elimina ese problema: la conexión la abre tu app. Requiere modo Thick (<code>oracledb.init_oracle_client()</code> con Instant Client 19.4 o superior) y base 19.4 o superior. En modo Thin, el por defecto de python-oracledb, CQN funciona pero solo en el modo clásico.</li>
<li><strong><code>job_queue_processes</code></strong> mayor que cero en la base: las notificaciones se despachan por el job queue.</li>
</ul>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong><code>ORA-29977: Unsupported column type for query registration in guaranteed mode</code></strong>: en modo <em>query</em> hay tipos de columna que Oracle no admite (LOB, entre otros), y <code>SELECT *</code> los arrastra. Lista las columnas que necesitas o quita <code>SUBSCR_QOS_QUERY</code> y trabaja en modo <em>object</em>.</li>
<li><strong>Las notificaciones no son durables</strong>: si tu servicio está caído durante un cambio, no lo recibe. Al arrancar, haz una reconciliación (por <code>updated_at</code>, o completa) y después suscríbete. <code>timeout=0</code> en <code>subscribe</code> mantiene la suscripción hasta que la conexión muera.</li>
<li><strong>Una suscripción por conexión viva</strong>: la conexión con <code>events=True</code> tiene que seguir abierta; ponle keepalives y reconecta con re-suscripción si se cae.</li>
<li><strong>Mucho volumen</strong> (miles de commits por segundo) no es territorio de CQN: ahí entra un CDC por redo log (GoldenGate, o Debezium con su conector de Oracle vía LogMiner).</li>
<li><strong><code>SELECT</code> con funciones no deterministas</strong> o sobre vistas complejas puede no registrarse en modo <em>query</em>; simplifica la consulta o cae a modo <em>object</em>.</li>
</ul>
<p>Referencia: <a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/cqn.html">Continuous Query Notification</a> en la documentación de python-oracledb y la <a href="https://github.com/laurenz/oracle_fdw/issues/342">discusión en oracle_fdw</a>.</p>
]]></content:encoded></item><item><title>pgbouncer: 'unsupported startup parameter: extra_float_digits' (y 'no such database')</title><link>https://inet.sh/es/posts/databases/pgbouncer-unsupported-startup-parameter/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/databases/pgbouncer-unsupported-startup-parameter/</guid><description>JDBC, DBeaver, DataGrip, Npgsql o psycopg conectan directo a PostgreSQL pero a través de pgbouncer fallan con &amp;#39;unsupported startup parameter: extra_float_digits&amp;#39; (o &amp;#39;options&amp;#39;). pgbouncer rechaza los parámetros de sesión que no puede rastrear entre clientes; ignore_startup_parameters los acepta y descarta. Qué es seguro ignorar, qué no, track_extra_parameters, y el &amp;#39;pooler error: no such database&amp;#39; de la sección [databases].</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1">; /etc/pgbouncer/pgbouncer.ini</span>
</span></span><span class="line"><span class="cl"><span class="k">[pgbouncer]</span>
</span></span><span class="line"><span class="cl"><span class="na">ignore_startup_parameters</span> <span class="o">=</span> <span class="s">extra_float_digits, options</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">systemctl reload pgbouncer          <span class="c1"># o RELOAD; en la consola de admin: psql -p 6432 -U pgbouncer pgbouncer</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>extra_float_digits</code> se puede ignorar sin efectos. <code>options</code> (lo que JDBC y DataGrip usan para <code>-c search_path=…</code>) también se ignora, pero entonces el <code>search_path</code> del cliente <strong>no llega</strong>: fíjalo en el rol con <code>alter role app set search_path = app, public</code>.</p>
<h2 id="el-error">El error</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">FATAL: unsupported startup parameter: extra_float_digits
</span></span></code></pre></td></tr></table>
</div>
</div><p>o, según el cliente, <code>pooler error: unsupported startup parameter: extra_float_digits</code>. Directo al puerto 5432 conecta; por el 6432 de pgbouncer, no. Lo mandan JDBC (todo lo que use el driver de PostgreSQL para Java: DBeaver, DataGrip, Spring, Jenkins), Npgsql y algunos ORMs; otros clientes mandan <code>options</code>.</p>
<h2 id="por-qué-pasa">Por qué pasa</h2>
<p>Cuando un cliente abre conexión, mete en el paquete de inicio los parámetros de sesión que quiere (<code>extra_float_digits=3</code>, <code>options=-c search_path=app</code>). pgbouncer, en modo <code>transaction</code> o <code>statement</code>, comparte una misma conexión de servidor entre muchos clientes, así que solo deja pasar los parámetros que sabe guardar por cliente y restaurar en el servidor cuando le toca a ese cliente: <code>client_encoding</code>, <code>datestyle</code>, <code>timezone</code>, <code>standard_conforming_strings</code>, <code>application_name</code>. Cualquier otro sería un ajuste que se filtra de un cliente a otro; antes que eso, pgbouncer rechaza la conexión.</p>
<p>Dos formas de resolverlo:</p>
<table>
  <thead>
      <tr>
          <th>Opción</th>
          <th>Qué hace</th>
          <th>Para qué parámetros</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>ignore_startup_parameters</code></td>
          <td>Acepta el parámetro y lo tira</td>
          <td>Los que no cambian nada (<code>extra_float_digits</code>) o los que puedes fijar en otro sitio (<code>options</code>)</td>
      </tr>
      <tr>
          <td><code>track_extra_parameters</code></td>
          <td>Lo guarda por cliente y lo restaura en el servidor, como hace con <code>timezone</code></td>
          <td>Parámetros que el servidor reporta al cliente (<code>ParameterStatus</code>) y que de verdad necesitas por sesión</td>
      </tr>
  </tbody>
</table>
<p><code>extra_float_digits</code> es inofensivo desde PostgreSQL 12: la salida de <code>float</code> ya es la representación más corta exacta y el parámetro no cambia nada. <code>options</code> con <code>search_path</code> <strong>sí</strong> importa: ignorado, el cliente verá el <code>search_path</code> por defecto de la base y las consultas sin esquema fallan con <code>relation does not exist</code>. La solución limpia es el rol:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">alter</span><span class="w"> </span><span class="k">role</span><span class="w"> </span><span class="n">app</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="n">search_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">app</span><span class="p">,</span><span class="w"> </span><span class="k">public</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="no-such-database">&rsquo;no such database'</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">pooler error: no such database: app_db
</span></span></code></pre></td></tr></table>
</div>
</div><p>pgbouncer solo enruta bases que estén en <code>[databases]</code>. O añades la entrada, o pones un comodín:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[databases]</span>
</span></span><span class="line"><span class="cl"><span class="na">app_db</span> <span class="o">=</span> <span class="s">host=db.example port=5432 dbname=app_db</span>
</span></span><span class="line"><span class="cl"><span class="na">*</span> <span class="o">=</span> <span class="s">host=db.example port=5432</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Con <code>*</code>, un cliente que pida <code>otra_db</code> obtiene <code>host=db.example dbname=otra_db</code>; esas entradas automáticas se limpian solas tras <code>autodb_idle_timeout</code>. Y comprueba el <code>dbname=</code> de la entrada: es habitual apuntar <code>app_db</code> a otra base por un copy-paste.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong><code>RELOAD</code> basta</strong> para <code>ignore_startup_parameters</code> y <code>[databases]</code>; <code>listen_*</code> y <code>auth_type</code> piden reinicio.</li>
<li><strong>Modo <code>transaction</code> y <code>SET</code></strong>: un <code>SET search_path</code> a mano en la sesión se pierde en cuanto la conexión de servidor pasa a otro cliente; usa <code>SET LOCAL</code> dentro de la transacción o el <code>alter role</code>.</li>
<li><strong>Prepared statements</strong> con nombre (JDBC <code>prepareThreshold</code>, <code>PREPARE</code>) no funcionan en modo <code>transaction</code> en pgbouncer antiguo; desde 1.21, <code>max_prepared_statements = 200</code> los soporta.</li>
<li><strong>SCRAM</strong>: con <code>auth_type = scram-sha-256</code> la <code>userlist.txt</code> necesita el hash SCRAM tal cual sale de <code>pg_authid</code>, o <code>auth_query</code> para que pgbouncer lo consulte.</li>
<li><code>SHOW CONFIG;</code> y <code>SHOW DATABASES;</code> en la consola de admin te dicen qué está cargado de verdad, no lo que crees haber escrito.</li>
</ul>
<p>Ver también: <a href="/es/posts/databases/postgresql-cheat-sheet-administracion/">PostgreSQL: cheat sheet de administración</a>. Referencia: <a href="https://www.pgbouncer.org/config.html">pgbouncer.ini</a>.</p>
]]></content:encoded></item><item><title>PostgreSQL: cheat sheet de administración (filas por tabla, tamaños, sesiones, usuario de solo lectura)</title><link>https://inet.sh/es/posts/databases/postgresql-cheat-sheet-administracion/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/databases/postgresql-cheat-sheet-administracion/</guid><description>Las consultas que un sysadmin repite en PostgreSQL: contar las filas de todas las tablas en una sola consulta (query_to_xml + xpath) o estimarlas al instante (n_live_tup), tamaños de base y tablas, quién está conectado y cómo expulsarlo, usuario de solo lectura con pg_read_all_data (14+) o con GRANT y ALTER DEFAULT PRIVILEGES, timezone por base o rol, y los flags de psql que evitan sustos.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- Filas exactas de todas las tablas de un esquema, en una sola consulta
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">select</span><span class="w"> </span><span class="n">table_schema</span><span class="p">,</span><span class="w"> </span><span class="k">table_name</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">(</span><span class="n">xpath</span><span class="p">(</span><span class="s1">&#39;/row/cnt/text()&#39;</span><span class="p">,</span><span class="w"> </span><span class="n">xml_count</span><span class="p">))[</span><span class="mi">1</span><span class="p">]::</span><span class="nb">text</span><span class="p">::</span><span class="nb">bigint</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="k">row_count</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">from</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">select</span><span class="w"> </span><span class="n">table_schema</span><span class="p">,</span><span class="w"> </span><span class="k">table_name</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">query_to_xml</span><span class="p">(</span><span class="n">format</span><span class="p">(</span><span class="s1">&#39;select count(*) as cnt from %I.%I&#39;</span><span class="p">,</span><span class="w"> </span><span class="n">table_schema</span><span class="p">,</span><span class="w"> </span><span class="k">table_name</span><span class="p">),</span><span class="w"> </span><span class="k">false</span><span class="p">,</span><span class="w"> </span><span class="k">true</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;&#39;</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">xml_count</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">from</span><span class="w"> </span><span class="n">information_schema</span><span class="p">.</span><span class="n">tables</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">where</span><span class="w"> </span><span class="n">table_schema</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;public&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="n">table_type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;BASE TABLE&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w"> </span><span class="n">t</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="k">row_count</span><span class="w"> </span><span class="k">desc</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- Estimación instantánea (no lee las tablas)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">select</span><span class="w"> </span><span class="n">relname</span><span class="p">,</span><span class="w"> </span><span class="n">n_live_tup</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">pg_stat_user_tables</span><span class="w"> </span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="n">n_live_tup</span><span class="w"> </span><span class="k">desc</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- Usuario de solo lectura (PostgreSQL 14 o superior)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">create</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="n">reporting</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="n">password</span><span class="w"> </span><span class="s1">&#39;…&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">grant</span><span class="w"> </span><span class="n">pg_read_all_data</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">reporting</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="contar-filas">Contar filas</h2>
<p><code>count(*)</code> exacto sobre cada tabla exige recorrerla entera; en una base de cientos de GB tarda. Lo que casi siempre quieres es la <strong>estimación</strong> que mantiene el autovacuum, que se actualiza en cada <code>ANALYZE</code> y suele estar a un par de puntos del real:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="n">schemaname</span><span class="p">,</span><span class="w"> </span><span class="n">relname</span><span class="p">,</span><span class="w"> </span><span class="n">n_live_tup</span><span class="p">,</span><span class="w"> </span><span class="n">n_dead_tup</span><span class="p">,</span><span class="w"> </span><span class="n">last_autoanalyze</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">from</span><span class="w"> </span><span class="n">pg_stat_user_tables</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="n">n_live_tup</span><span class="w"> </span><span class="k">desc</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Cuando necesitas el número exacto de <strong>todas</strong> las tablas sin escribir un <code>DO</code> en PL/pgSQL, el truco de <code>query_to_xml</code>: ejecuta un <code>count(*)</code> dinámico por tabla, devuelve el resultado como XML y <code>xpath</code> extrae el número. Es SQL plano, funciona desde cualquier cliente, y se puede filtrar por esquema o por nombre. Solo recuerda que corre los conteos en serie: en tablas grandes, paciencia.</p>
<h2 id="tamaños">Tamaños</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="n">pg_size_pretty</span><span class="p">(</span><span class="n">pg_database_size</span><span class="p">(</span><span class="n">current_database</span><span class="p">()));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">select</span><span class="w"> </span><span class="n">n</span><span class="p">.</span><span class="n">nspname</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="k">schema</span><span class="p">,</span><span class="w"> </span><span class="k">c</span><span class="p">.</span><span class="n">relname</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="k">table</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">pg_size_pretty</span><span class="p">(</span><span class="n">pg_total_relation_size</span><span class="p">(</span><span class="k">c</span><span class="p">.</span><span class="n">oid</span><span class="p">))</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">total</span><span class="p">,</span><span class="w">   </span><span class="c1">-- datos + índices + TOAST
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">       </span><span class="n">pg_size_pretty</span><span class="p">(</span><span class="n">pg_relation_size</span><span class="p">(</span><span class="k">c</span><span class="p">.</span><span class="n">oid</span><span class="p">))</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="k">data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">from</span><span class="w"> </span><span class="n">pg_class</span><span class="w"> </span><span class="k">c</span><span class="w"> </span><span class="k">join</span><span class="w"> </span><span class="n">pg_namespace</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">n</span><span class="p">.</span><span class="n">oid</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">c</span><span class="p">.</span><span class="n">relnamespace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">where</span><span class="w"> </span><span class="k">c</span><span class="p">.</span><span class="n">relkind</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;r&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="n">n</span><span class="p">.</span><span class="n">nspname</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;pg_catalog&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;information_schema&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="n">pg_total_relation_size</span><span class="p">(</span><span class="k">c</span><span class="p">.</span><span class="n">oid</span><span class="p">)</span><span class="w"> </span><span class="k">desc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">limit</span><span class="w"> </span><span class="mi">20</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="sesiones">Sesiones</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- Quién está conectado y qué hace
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">select</span><span class="w"> </span><span class="n">pid</span><span class="p">,</span><span class="w"> </span><span class="n">usename</span><span class="p">,</span><span class="w"> </span><span class="k">state</span><span class="p">,</span><span class="w"> </span><span class="n">client_addr</span><span class="p">,</span><span class="w"> </span><span class="n">application_name</span><span class="p">,</span><span class="w"> </span><span class="k">left</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"> </span><span class="mi">80</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">query</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">from</span><span class="w"> </span><span class="n">pg_stat_activity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">where</span><span class="w"> </span><span class="n">datname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">current_database</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- Cancelar una consulta (la sesión sigue)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">select</span><span class="w"> </span><span class="n">pg_cancel_backend</span><span class="p">(</span><span class="mi">12345</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- Expulsar a todos menos a ti (antes de un DROP o un restore)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">select</span><span class="w"> </span><span class="n">pg_terminate_backend</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">from</span><span class="w"> </span><span class="n">pg_stat_activity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">where</span><span class="w"> </span><span class="n">datname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;app_db&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="n">pid</span><span class="w"> </span><span class="o">&lt;&gt;</span><span class="w"> </span><span class="n">pg_backend_pid</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- O, desde PostgreSQL 13, directo:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">drop</span><span class="w"> </span><span class="k">database</span><span class="w"> </span><span class="n">app_db</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="p">(</span><span class="k">force</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- Uptime
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">select</span><span class="w"> </span><span class="n">date_trunc</span><span class="p">(</span><span class="s1">&#39;second&#39;</span><span class="p">,</span><span class="w"> </span><span class="k">current_timestamp</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">pg_postmaster_start_time</span><span class="p">())</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">uptime</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="usuario-de-solo-lectura">Usuario de solo lectura</h2>
<p>En <strong>14 o superior</strong>, un rol predefinido lo resuelve para todos los esquemas, incluidas las tablas que se creen mañana:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">create</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="n">reporting</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="n">password</span><span class="w"> </span><span class="s1">&#39;…&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">grant</span><span class="w"> </span><span class="n">pg_read_all_data</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">reporting</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>En versiones anteriores, o si quieres limitarlo a un esquema, el camino clásico:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">grant</span><span class="w"> </span><span class="k">connect</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">database</span><span class="w"> </span><span class="n">app_db</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">reporting</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">grant</span><span class="w"> </span><span class="k">usage</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">schema</span><span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">reporting</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">grant</span><span class="w"> </span><span class="k">select</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">all</span><span class="w"> </span><span class="n">tables</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">schema</span><span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">reporting</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- tablas futuras: solo cubre las que cree el rol que ejecuta esto (o FOR ROLE app_owner)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">alter</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="k">privileges</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="k">role</span><span class="w"> </span><span class="n">app_owner</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">schema</span><span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="k">grant</span><span class="w"> </span><span class="k">select</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">tables</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">reporting</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- Deshacer
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">revoke</span><span class="w"> </span><span class="k">all</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">all</span><span class="w"> </span><span class="n">tables</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">schema</span><span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">reporting</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">alter</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="k">privileges</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="k">role</span><span class="w"> </span><span class="n">app_owner</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">schema</span><span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="k">revoke</span><span class="w"> </span><span class="k">select</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="n">tables</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">reporting</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>alter default privileges</code> no toca lo que ya existe y solo aplica a objetos creados por el rol indicado: si otra cuenta crea tablas, <code>reporting</code> no las verá. De ahí el valor de <code>pg_read_all_data</code>.</p>
<h2 id="contraseñas-timezone-y-psql">Contraseñas, timezone y psql</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="err">\</span><span class="n">password</span><span class="w"> </span><span class="n">app_user</span><span class="w">                                  </span><span class="c1">-- desde psql, sin dejar la contraseña en el historial
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">alter</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="n">app_user</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="n">password</span><span class="w"> </span><span class="s1">&#39;…&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">alter</span><span class="w"> </span><span class="k">database</span><span class="w"> </span><span class="n">app_db</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="n">timezone</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="s1">&#39;America/Mexico_City&#39;</span><span class="p">;</span><span class="w">   </span><span class="c1">-- nuevas conexiones a esa base
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">alter</span><span class="w"> </span><span class="k">role</span><span class="w"> </span><span class="n">reporting</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="n">timezone</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="s1">&#39;UTC&#39;</span><span class="p">;</span><span class="w">                    </span><span class="c1">-- nuevas sesiones de ese rol
</span></span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">psql -v <span class="nv">ON_ERROR_STOP</span><span class="o">=</span><span class="m">1</span> -f data.sql app_db      <span class="c1"># un script que se detiene en el primer error</span>
</span></span><span class="line"><span class="cl">psql -P <span class="nv">pager</span><span class="o">=</span>off app_db                        <span class="c1"># sin paginador (o \pset pager 0 dentro)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Y dentro de <code>psql</code>, <code>\x auto</code> (columnas verticales cuando no caben) y <code>\timing</code>.</p>
<h2 id="mantenimiento-mínimo">Mantenimiento mínimo</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">vacuum</span><span class="w"> </span><span class="p">(</span><span class="k">verbose</span><span class="p">,</span><span class="w"> </span><span class="k">analyze</span><span class="p">)</span><span class="w"> </span><span class="n">orders</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">reindex</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="n">concurrently</span><span class="w"> </span><span class="n">orders</span><span class="p">;</span><span class="w">               </span><span class="c1">-- 12+, sin bloquear escrituras
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>VACUUM FULL</code> reescribe la tabla y la bloquea mientras dura; para recuperar espacio en producción, <code>pg_repack</code>.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong><code>n_live_tup</code> es una estimación</strong>: recién analizada es fiel; en una tabla con mucho churn puede desviarse hasta el siguiente autoanalyze.</li>
<li><strong><code>pg_read_all_data</code> no salta RLS</strong> ni concede DDL; es lectura de tablas, vistas y secuencias, nada más.</li>
<li><strong><code>pg_terminate_backend</code> corta transacciones</strong>: la aplicación ve un error y reconecta. Para solo abortar una consulta, <code>pg_cancel_backend</code>.</li>
<li><strong><code>alter role … set timezone</code></strong> aplica a sesiones nuevas; la que ya está abierta sigue igual.</li>
<li><strong><code>quote_ident</code>/<code>format('%I')</code></strong> siempre que construyas SQL con nombres de tablas: un nombre con mayúsculas o guiones rompe la consulta sin ellos.</li>
</ul>
<h2 id="ver-también">Ver también</h2>
<ul>
<li><a href="/es/posts/databases/pgbouncer-unsupported-startup-parameter/">pgbouncer: unsupported startup parameter</a></li>
<li><a href="/es/posts/gcp/cloud-sql-importar-dumps-definer-gtid-extensiones/">Cloud SQL: importar dumps sin errores de DEFINER, GTID o extensiones</a></li>
</ul>
]]></content:encoded></item></channel></rss>