Error tables declared with oids are not supported

My site is no longer loading with a recent upgrade for PostgreSQL. WITH OIDs has actually been deprecated since 2005. There was a mailing list announcement from 2018 that has some details: https://...

My site is no longer loading with a recent upgrade for PostgreSQL.

WITH OIDs has actually been deprecated since 2005. There was a mailing list
announcement from 2018 that has some details:
https://www.postgresql.org/message-id/E1gPG3j-0003cy-03%40gemulon.postgresql.org

== ERROR-REPORT (DEBUGGING ENABLED) ==
== (When you copy this debug output to a forum or other places, make sure to remove your username/passwords, as they may be contained within function calls) ==

#0  errorToExceptionHandler(2, pg_query(): Query failed: ERROR:  tables declared WITH OIDS are not supported, /srv/www/serendipity/include/db/postgres.inc.php, 224)
#1  pg_query(Resource id #21, SET default_with_oids = true) called at [/srv/www/serendipity/include/db/postgres.inc.php:224]
#2  serendipity_db_query(SET default_with_oids = true, 1, both, , , , 1) called at [/srv/www/serendipity/include/db/postgres.inc.php:218]
#3  serendipity_db_query(SELECT name, value
                                        FROM serendipity_config
                                        WHERE authorid = 0) called at [/srv/www/serendipity/include/functions_config.inc.php:314]
#4  serendipity_load_configuration() called at [/srv/www/serendipity/serendipity_config.inc.php:339]
#5  include(/srv/www/serendipity/serendipity_config.inc.php) called at [/srv/www/serendipity/serendipity_admin.php:9]

Warning: pg_query(): Query failed: ERROR: tables declared WITH OIDS are not supported in /srv/www/serendipity/include/db/postgres.inc.php on line 224.

 == ERROR-REPORT (DEBUGGING ENABLED) == 

 == (When you copy this debug output to a forum or other places, make sure to remove your username/passwords, as they may be contained within function calls) == 

#0  errorToExceptionHandler(1, Uncaught ErrorException: Warning: pg_query(): Query failed: ERROR:  tables declared WITH OIDS are not supported in /srv/www/serendipity/include/db/postgres.inc.php:224
Stack trace:
#0 [internal function]: errorToExceptionHandler()
#1 /srv/www/serendipity/include/db/postgres.inc.php(224): pg_query()
#2 /srv/www/serendipity/include/db/postgres.inc.php(218): serendipity_db_query()
#3 /srv/www/serendipity/include/functions_config.inc.php(314): serendipity_db_query()
#4 /srv/www/serendipity/serendipity_config.inc.php(339): serendipity_load_configuration()
#5 /srv/www/serendipity/serendipity_admin.php(9): include('...')
#6 {main}
  thrown, /srv/www/serendipity/include/db/postgres.inc.php, 224) called at [/srv/www/serendipity/include/compat.inc.php:254]
#1  fatalErrorShutdownHandler()

Fatal Error: Uncaught ErrorException: Warning: pg_query(): Query failed: ERROR:  tables declared WITH OIDS are not supported in /srv/www/serendipity/include/db/postgres.inc.php:224
Stack trace:
#0 [internal function]: errorToExceptionHandler()
#1 /srv/www/serendipity/include/db/postgres.inc.php(224): pg_query()
#2 /srv/www/serendipity/include/db/postgres.inc.php(218): serendipity_db_query()
#3 /srv/www/serendipity/include/functions_config.inc.php(314): serendipity_db_query()
#4 /srv/www/serendipity/serendipity_config.inc.php(339): serendipity_load_configuration()
#5 /srv/www/serendipity/serendipity_admin.php(9): include('...')
#6 {main}
  thrown in /srv/www/serendipity/include/db/postgres.inc.php on line 224.

Rafael Bernard Araújo

I have some PostgreSQL databases running pretty well but we need to keep our software updated. This is a mandatory practice for a high-quality service. Those servers are running version 10 and they need to be upgraded to version 12. I have used pg_dump / pg_restore strategy for a long time, but this time I would rather use pg_upgrade.

Let’s dive into how to do it.

Table of contents:

  1. Install
  2. InitDB
  3. Check upgrade consistency
  4. Set locale
  5. Upgrade
  6. Configurations

Install

The package postgresql12-server contains everything needed to run the server, but my databases use some extensions [1], then I will add postgresql12-devel and postgresql12-contrib to be able to compile and to install the extensions.

yum install postgresql12-server postgresql12-devel postgresql12-contrib

Enter fullscreen mode

Exit fullscreen mode

InitDB

After installation we need to setup new server with initdb:

~% /usr/pgsql-12/bin/postgresql-12-setup initdb
Initializing database … OK

Enter fullscreen mode

Exit fullscreen mode

Check upgrade consistency

We need to check compatibility. Turn to postgres user (su - postgres) and run the command:

~% su - postgres
~% /usr/pgsql-12/bin/pg_upgrade --old-bindir=/usr/pgsql-10/bin --new-bindir=/usr/pgsql-12/bin --old-datadir=/var/lib/pgsql/10/data --new-datadir=/var/lib/pgsql/12/data --check

Performing Consistency Checks on Old Live Server
-----------------------------------------------------
Checking cluster versions ok
Checking database user is the install user ok
Checking database connection settings ok
Checking for prepared transactions ok
Checking for reg* data types in user tables ok
Checking for contrib/isn with bigint-passing mismatch ok
Checking for tables WITH OIDS fatal

Your installation contains tables declared WITH OIDS, which is not supported
anymore. Consider removing the oid column using
    ALTER TABLE ... SET WITHOUT OIDS;
A list of tables with the problem is in the file:
    tables_with_oids.txt

Failure, exiting

Enter fullscreen mode

Exit fullscreen mode

As you may see, I got a fatal error, indicating that the upgrade is not possible. In my case, tables with OIDs are the culprit. In your case could be something else. In any case, we need to fix before upgrading.

I fixed tables removing OIDs on mentioned tables. And ran check again:

Performing Consistency Checks on Old Live Server
-----------------------------------------------------
Checking cluster versions ok
Checking database user is the install user ok
Checking database connection settings ok
Checking for prepared transactions ok
Checking for reg* data types in user tables ok
Checking for contrib/isn with bigint-passing mismatch ok
Checking for tables WITH OIDS ok
Checking for invalid "sql_identifier" user columns ok
Checking for presence of required libraries ok
Checking database user is the install user ok
Checking for prepared transactions ok

*Clusters are compatible*

Enter fullscreen mode

Exit fullscreen mode

Yay!

Set locale

There is a tricky configuration that is not detected by pg_upgrade check but it is very important to me. I use C locale on my databases [2], then I need to perform an extra step. If this is your case, you may follow the same steps applying yours.

I need to stop postgresql10 and start postgresql12:

systemctl stop postgresql-10.service
systemctl start postgresql-12.service

Enter fullscreen mode

Exit fullscreen mode

Then I run locale change at my template1 then locale will be enabled when my database will be upgraded.

UPDATE pg_database SET datcollate='C', datctype='C' WHERE datname='template1';

Enter fullscreen mode

Exit fullscreen mode

And stop again: systemctl stop postgresql-12.service to be ready to upgrade.

Upgrade

Upgrade command is the same that we run before, without --check flag.

~% su - postgres
~% /usr/pgsql-12/bin/pg_upgrade --old-bindir=/usr/pgsql-10/bin --new-bindir=/usr/pgsql-12/bin --old-datadir=/var/lib/pgsql/10/data --new-datadir=/var/lib/pgsql/12/data

Performing Consistency Checks
----------------------------------
Checking cluster versions ok
Checking database user is the install user ok
Checking database connection settings ok
Checking for prepared transactions ok
Checking for reg* data types in user tables ok
Checking for contrib/isn with bigint-passing mismatch ok
Checking for tables WITH OIDS ok
Checking for invalid "sql_identifier" user columns ok
Creating dump of global objects ok
Creating dump of database schemas
                                                            ok
Checking for presence of required libraries ok
Checking database user is the install user ok
Checking for prepared transactions ok

If pg_upgrade fails after this point, you must re-initdb the
new cluster before continuing.

Performing Upgrade
-----------------------
Analyzing all rows in the new cluster ok
Freezing all rows in the new cluster ok
Deleting files from new pg_xact ok
Copying old pg_xact to new server ok
Setting next transaction ID and epoch for new cluster ok
Deleting files from new pg_multixact/offsets ok
Copying old pg_multixact/offsets to new server ok
Deleting files from new pg_multixact/members ok
Copying old pg_multixact/members to new server ok
Setting next multixact ID and offset for new cluster ok
Resetting WAL archives ok
Setting frozenxid and minmxid counters in new cluster ok
Restoring global objects in the new cluster ok
Restoring database schemas in the new cluster
                                                            ok
Copying user relation files
                                                            ok
Setting next OID for new cluster ok
Sync data directory to disk ok
Creating script to analyze new cluster ok
Creating script to delete old cluster ok

Upgrade Complete
---------------------
Optimizer statistics are not transferred by pg_upgrade so,
once you start the new server, consider running:
    ./analyze_new_cluster.sh

Running this script will delete the old cluster's data files:
    ./delete_old_cluster.sh

Enter fullscreen mode

Exit fullscreen mode

Consider running analyze_new_cluster. Optional but nice to have.

vacuumdb: processing database "mydb": Generating minimal optimizer statistics (1 target)
vacuumdb: processing database "postgres": Generating minimal optimizer statistics (1 target)
vacuumdb: processing database "template1": Generating minimal optimizer statistics (1 target)
vacuumdb: processing database "mydb": Generating medium optimizer statistics (10 targets)
vacuumdb: processing database "postgres": Generating medium optimizer statistics (10 targets)
vacuumdb: processing database "template1": Generating medium optimizer statistics (10 targets)
vacuumdb: processing database "mydb": Generating default (full) optimizer statistics
vacuumdb: processing database "postgres": Generating default (full) optimizer statistics
vacuumdb: processing database "template1": Generating default (full) optimizer statistics

Done

Enter fullscreen mode

Exit fullscreen mode

Configurations

Before deleting your old cluster, remember to get some of your configurations.

mv /var/lib/pgsql/12/data/pg_hba.conf /var/lib/pgsql/12/data/pg_hba.conf.new
cp /var/lib/pgsql/10/data/pg_hba.conf /var/lib/pgsql/12/data/

Enter fullscreen mode

Exit fullscreen mode

And add

# Add settings for extensions here
include_dir = '/var/lib/pgsql/conf.d' # include files ending in '.conf' from

Enter fullscreen mode

Exit fullscreen mode

Then you may delete your old cluster. 🙂

Links:

  1. Extensions: https://www.postgresql.org/docs/current/contrib.html
  2. Locale: https://www.postgresql.org/docs/current/locale.html

Hey 😍

Want to help the DEV Community feel more like a community?

Head over to the Welcome Thread and greet some new community members!

It only takes a minute of your time, and goes a long way!

Read next


yrashk profile image

Why not Rust for Omnigres?

Yurii Rashkovskii — Jan 6


ne1ro profile image

Bitemporality, or how to change the past

Alex Kuznetsov — Jan 6


mohsenkamrani profile image

Top 3 Most Popular Databases Among Web Developers and When to Use Them

mohsen — Jan 6


wizarddos profile image

Quick guide for PHP prepared statements

Mateusz Jasiński — Jan 5

Once unpublished, all posts by rafaelbernard will become hidden and only accessible to themselves.

If rafaelbernard is not suspended, they can still re-publish their posts from their dashboard.

Note:

Once unpublished, this post will become invisible to the public and only accessible to Rafael Bernard Araújo.

They can still re-publish the post if they are not suspended.

Thanks for keeping DEV Community 👩‍💻👨‍💻 safe. Here is what you can do to flag rafaelbernard:

Make all posts by rafaelbernard less visible

rafaelbernard consistently posts content that violates DEV Community 👩‍💻👨‍💻’s
code of conduct because it is harassing, offensive or spammy.

PostgreSQL online editor

Write, Run & Share PostgreSQL queries online using OneCompiler’s PostgreSQL online editor and compiler for free. It’s one of the robust, feature-rich online editor and compiler for PostgreSQL. Getting started with the OneCompiler’s PostgreSQL editor is really simple and pretty fast. The editor shows sample boilerplate code when you choose database as ‘PostgreSQL’ and start writing queries to learn and test online without worrying about tedious process of installation.

About PostgreSQL

PostgreSQL is a open source relational database system and is also knows as Postgres.

Key Features:

  • Postgres is not only free and open-source but also it is highly extensible.
  • Custom Data types and funtions from various programming languaues can be introduced and the good part is compiling entire database is not required.
  • ACID(Atomicity, Consistency, Isolation, Durability) compliant.
  • First DBMS which implemented Multi-version concurrency control (MVCC) feature.
  • It’s the default database server for MacOS.
  • It supports all major operating systems like Linux, Windows, OpenBSD,FreeBSD etc.

Syntax help

1. CREATE

CREATE command is used to create a table, schema or an index.

Syntax:

         CREATE TABLE table_name (
                column1 datatype,
                column2 datatype,
                ....);

2. ALTER

ALTER command is used to add, modify or delete columns or constraints from the database table.

Syntax

ALTER TABLE Table_name ADD column_name datatype;

3. TRUNCATE:

TRUNCATE command is used to delete the data present in the table but this will not delete the table.

Syntax

TRUNCATE table table_name;

4. DROP

DROP command is used to delete the table along with its data.

Syntax

DROP TABLE table_name;

5. RENAME

RENAME command is used to rename the table name.

Syntax

ALTER TABLE table_name1 RENAME to new_table_name1; 

6. INSERT

INSERT Statement is used to insert new records into the database table.

Syntax

INSERT INTO table_name (column1, column2, column3, ...) VALUES (value1, value2, value3, ...);

7. SELECT

Select statement is used to select data from database tables.

Syntax:

SELECT column1, column2, ...
FROM table_name; 

8. UPDATE

UPDATE statement is used to modify the existing values of records present in the database table.

Syntax

UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition; 

9. DELETE

DELETE statement is used to delete the existing records present in the database table.

Syntax

DELETE FROM table_name where condition;

Let’s go into details:

tableoid

The tableoid is the OID from the table where the row is originally coming from. In a simple table, this is just the OID of the table itself.

oid=# CREATE TABLE show_oid (id INT);
CREATE TABLE
oid=# SELECT 'public.show_oid'::regclass::oid;
  oid  
-------
 79824
(1 row)

oid=# INSERT INTO show_oid VALUES (1), (2);
INSERT 0 2
oid=# SELECT tableoid, id FROM show_oid;
 tableoid | id 
----------+----
    79824 |  1
    79824 |  2
(2 rows)

All rows are coming from the table with the oid = 79824.

This changes once partitioning is used. In PostgreSQL, a partition is an inherited table. Simply said, the data lives in a child table (the partition), and when the parent table is queried, PostgreSQL also searches all child tables for data. Here is an example with partitions. First let’s create a parent table, and partition it by range:

oid=# DROP TABLE IF EXISTS show_oid;
DROP TABLE
oid=# CREATE TABLE show_oid (id INT) PARTITION BY RANGE (id);
CREATE TABLE
oid=# CREATE TABLE show_oid_p1 PARTITION OF show_oid
oid-#    FOR VALUES FROM (1) TO (10);
CREATE TABLE
oid=# CREATE TABLE show_oid_p2 PARTITION OF show_oid
oid-#    FOR VALUES FROM (10) TO (20);
CREATE TABLE
oid=# CREATE TABLE show_oid_p3 PARTITION OF show_oid
oid-#    FOR VALUES FROM (20) TO (30);
CREATE TABLE
oid=# SELECT 'public.show_oid'::regclass::oid AS show_oid,
oid-#        'public.show_oid_p1'::regclass::oid AS show_oid_p1,
oid-#        'public.show_oid_p2'::regclass::oid AS show_oid_p2,
oid-#        'public.show_oid_p3'::regclass::oid AS show_oid_p3;
 show_oid | show_oid_p1 | show_oid_p2 | show_oid_p3 
----------+-------------+-------------+-------------
    79827 |       79830 |       79833 |       79836
(1 row)

We can see that each partition has a different oid. Now let’s insert some data:

oid=# INSERT INTO show_oid VALUES (1), (2), (9), (10), (11), (19), (20), (21), (29);
INSERT 0 9
oid=# SELECT tableoid, id FROM show_oid ORDER BY tableoid, id;
 tableoid | id 
----------+----
    79830 |  1
    79830 |  2
    79830 |  9
    79833 | 10
    79833 | 11
    79833 | 19
    79836 | 20
    79836 | 21
    79836 | 29
(9 rows)

Even though the data is selected from the parent table (show_oid, oid = 79827), there is not a single row returning data from this table. All the data comes from one of the partitions. During inserting the data, PostgreSQL moved the data into the partition, according to the partition key. The parent table stays empty:

oid=# SELECT tableoid, id FROM ONLY show_oid ORDER BY tableoid, id;
 tableoid | id 
----------+----
(0 rows)

The ONLY keyword here tells PostgreSQL not to scan any inherited tables. Since the parent table is empty, no rows are returned.

xmin + xmax

The xmin and xmax columns are holding transaction IDs. xmin shows when the row becomes visible (minimum required transaction ID), xmax shows when the row is no longer visible (maximum valid transaction ID). In a relational database like PostgreSQL, there can be many ongoing transactions at the same time. Every transaction has it’s own transaction ID, which can be queried:

oid=# SELECT txid_current();
 txid_current 
--------------
         7034
(1 row)

oid=# SELECT txid_current();
 txid_current 
--------------
         7035
(1 row)

The example above is wasting two transaction IDs: every command in PostgreSQL is wrapped in a transaction. If you don’t start a transaction, PostgreSQL will automatically wrap every command in a single transaction. That is the reason why the transaction ID in the example is increasing with every call of the txid_current() function.

Back to our problem: every transaction can change data. The transaction isolation level specifies which transaction can see which changes. Once data is committed, every later transaction can always see the changes — that part is easy. For ongoing transactions, the transaction ID specifies which changes become visible at which point. Let’s look at an example. First we need a table:

oid=# CREATE TABLE show_xmin_xmax (id INT PRIMARY KEY);
CREATE TABLE

Then we start a transaction and insert data:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7058
(1 row)

oid=# INSERT INTO show_xmin_xmax VALUES (1), (2);
INSERT 0 2
oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7058 |    0 |  1
 7058 |    0 |  2
(2 rows)

oid=# COMMIT;
COMMIT

The transaction ID is 7058, and this value appears in the xmin column. The xmax column is 0, because this data is not deleted (or updated).

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7059
(1 row)

oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7058 |    0 |  1
 7058 |    0 |  2
(2 rows)

oid=# UPDATE show_xmin_xmax SET id = 3 WHERE id = 1;
UPDATE 1
oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7058 |    0 |  2
 7059 |    0 |  3
(2 rows)

oid=# COMMIT;
COMMIT

Once the data is updated, a new row version appears, with xmin = 7059. What we can’t see here is that the old row (id = 1) is also still in the table, however the xmax value is now set to 7059 as well, and becomes invisible to the current and any newer transaction. Right now we can’t see this deleted row, however it is still there. Later in this blog post we learn how to make this data visible.

There is however a trick to make the changes visible when deleting data:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7060
(1 row)

oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7058 |    0 |  2
 7059 |    0 |  3
(2 rows)

oid=# DELETE FROM show_xmin_xmax WHERE id = 2 RETURNING xmin, xmax, *;
 xmin | xmax | id 
------+------+----
 7058 | 7060 |  2
(1 row)

DELETE 1
oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7059 |    0 |  3
(1 row)

oid=# COMMIT;
COMMIT

The RETURNING clause for DELETE can also return the xmin and xmax columns. As we can see in this example, the returnes data has xmax set to 7060 — the current transaction ID. Afterwards the deleted data is no longer visible for regular queries.

cmin + cmax

Like xmin and xmax, cmin and cmax store transaction information. However these two columns store the command sequence number inside the transaction. Imagine you start a transaction and execute multiple commands: every time a command changes data, this changes are stored with an increased cmin and cmax information. This is useful to trace back which changes happened at which point during a transaction, and it helps replaying changes in the correct order. Example table:

oid=# CREATE TABLE show_cmin_cmax (id INT PRIMARY KEY);
CREATE TABLE

Let’s insert some data, but this time using multiple commands in a transaction:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7042
(1 row)

oid=# INSERT INTO show_cmin_cmax VALUES (1), (2);
INSERT 0 2
oid=# INSERT INTO show_cmin_cmax VALUES (3), (4);
INSERT 0 2
oid=# INSERT INTO show_cmin_cmax VALUES (5), (6);
INSERT 0 2
oid=# SELECT xmin, xmax, cmin, cmax, id FROM show_cmin_cmax order by id;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 |    0 |    0 |    0 |  1
 7042 |    0 |    0 |    0 |  2
 7042 |    0 |    1 |    1 |  3
 7042 |    0 |    1 |    1 |  4
 7042 |    0 |    2 |    2 |  5
 7042 |    0 |    2 |    2 |  6
(6 rows)

oid=# COMMIT;
COMMIT

We can see that xmin for all rows is 7042, however cmin and cmax are increased with every INSERT. Data which was inserted using the same INSERT command has the same cmin and cmax number. Let’s update some data:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7043
(1 row)

oid=# SELECT xmin, xmax, cmin, cmax, id FROM show_cmin_cmax order by id;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 |    0 |    0 |    0 |  1
 7042 |    0 |    0 |    0 |  2
 7042 |    0 |    1 |    1 |  3
 7042 |    0 |    1 |    1 |  4
 7042 |    0 |    2 |    2 |  5
 7042 |    0 |    2 |    2 |  6
(6 rows)

oid=# UPDATE show_cmin_cmax SET id = 10 WHERE id = 1;
UPDATE 1
oid=# UPDATE show_cmin_cmax SET id = 20 WHERE id = 2;
UPDATE 1
oid=# SELECT xmin, xmax, cmin, cmax, id FROM show_cmin_cmax order by id;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 |    0 |    1 |    1 |  3
 7042 |    0 |    1 |    1 |  4
 7042 |    0 |    2 |    2 |  5
 7042 |    0 |    2 |    2 |  6
 7043 |    0 |    0 |    0 | 10
 7043 |    0 |    1 |    1 | 20
(6 rows)

oid=# COMMIT;
COMMIT

We can see how the transaction ID in xmin changes, because the UPDATE happens in a new transaction (7043). And the cmin and cmax again start at zero. Let’s also try the trick with DELETE and RETURNING:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7044
(1 row)

oid=# SELECT xmin, xmax, cmin, cmax, id FROM show_cmin_cmax order by id;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 |    0 |    1 |    1 |  3
 7042 |    0 |    1 |    1 |  4
 7042 |    0 |    2 |    2 |  5
 7042 |    0 |    2 |    2 |  6
 7043 |    0 |    0 |    0 | 10
 7043 |    0 |    1 |    1 | 20
(6 rows)

oid=# DELETE FROM show_cmin_cmax WHERE id = 3 RETURNING xmin, xmax, cmin, cmax, *;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 | 7044 |    0 |    0 |  3
(1 row)

DELETE 1
oid=# DELETE FROM show_cmin_cmax WHERE id = 4 RETURNING xmin, xmax, cmin, cmax, *;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 | 7044 |    1 |    1 |  4
(1 row)

DELETE 1
oid=# SELECT xmin, xmax, cmin, cmax, id FROM show_cmin_cmax order by id;
 xmin | xmax | cmin | cmax | id 
------+------+------+------+----
 7042 |    0 |    2 |    2 |  5
 7042 |    0 |    2 |    2 |  6
 7043 |    0 |    0 |    0 | 10
 7043 |    0 |    1 |    1 | 20
(4 rows)

oid=# COMMIT;
COMMIT

Because both deletes happen in the same transaction (7044), both returned rows show this transaction ID as xmax. cmin and cmax is increasing for every command.

ctid

The ctid column consists of two parts: the page or block in the table, and the position in this page. Let’s show at a slightly more complex example:

oid=# CREATE TABLE show_ctid (id INT PRIMARY KEY, data TEXT);
CREATE TABLE
oid=# ALTER TABLE show_ctid ALTER COLUMN data SET STORAGE plain;
ALTER TABLE
oid=# INSERT INTO show_ctid VALUES (1, 'Lorem'), (2, 'ipsum'), (3, 'dolor'), (4, 'sit'), (5, 'amet');
INSERT 0 5

This table has an integer as primary key, and a text field as data field. The first 5 words from Lorem ipsum are used as text. The storage type for the column is changed to «plain», to keep the data uncompressed and in the table itself, and don’t «outsource» it into a TOAST table.

oid=# d+ show_ctid
                                 Table "public.show_ctid"
 Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
--------+---------+-----------+----------+---------+---------+--------------+-------------
 id     | integer |           | not null |         | plain   |              | 
 data   | text    |           |          |         | plain   |              | 
Indexes:
    "show_ctid_pkey" PRIMARY KEY, btree (id)
Access method: heap

Let’s look at the ctid:

oid=# SELECT ctid, id, data FROM show_ctid order by id;
 ctid  | id | data  
-------+----+-------
 (0,1) |  1 | Lorem
 (0,2) |  2 | ipsum
 (0,3) |  3 | dolor
 (0,4) |  4 | sit
 (0,5) |  5 | amet
(5 rows)

Page 0, positions 1 to 5. But that’s not really helpful, isn’t it? Luckily there is a very useful extension which allows us to peek into the details: pageinspect. It comes in PostgreSQL contrib, and should be available on every system. You need to install it:

oid=# CREATE EXTENSION IF NOT EXISTS pageinspect;
CREATE EXTENSION

Now we can use the get_raw_page() function to select the page, and decode it using heap_page_items():

oid=# SELECT *
oid-#   FROM heap_page_items(get_raw_page('show_ctid', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |         t_data         
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------------------
  1 |   8152 |        1 |     34 |   7049 |      0 |        0 | (0,1)  |           2 |       2306 |     24 |        |       | x010000000d4c6f72656d
  2 |   8112 |        1 |     34 |   7049 |      0 |        0 | (0,2)  |           2 |       2306 |     24 |        |       | x020000000d697073756d
  3 |   8072 |        1 |     34 |   7049 |      0 |        0 | (0,3)  |           2 |       2306 |     24 |        |       | x030000000d646f6c6f72
  4 |   8040 |        1 |     32 |   7049 |      0 |        0 | (0,4)  |           2 |       2306 |     24 |        |       | x0400000009736974
  5 |   8000 |        1 |     33 |   7049 |      0 |        0 | (0,5)  |           2 |       2306 |     24 |        |       | x050000000b616d6574
(5 rows)

There is t_ctid, which is the position in the page. There are a couple more details, described here. And there is t_data, which holds the row data. For decoding the data, we need to have a bit of knowledge what this data is. First of all, the data is a bytea, and is therefore hex-encoded. This can be decoded:

oid=# SELECT *,
oid-#        encode(t_data, 'escape') AS decoded
oid-#   FROM heap_page_items(get_raw_page('show_ctid', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |         t_data         |           decoded           
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------------------+-----------------------------
  1 |   8152 |        1 |     34 |   7049 |      0 |        0 | (0,1)  |           2 |       2306 |     24 |        |       | x010000000d4c6f72656d | x01000000rLorem
  2 |   8112 |        1 |     34 |   7049 |      0 |        0 | (0,2)  |           2 |       2306 |     24 |        |       | x020000000d697073756d | x02000000ripsum
  3 |   8072 |        1 |     34 |   7049 |      0 |        0 | (0,3)  |           2 |       2306 |     24 |        |       | x030000000d646f6c6f72 | x03000000rdolor
  4 |   8040 |        1 |     32 |   7049 |      0 |        0 | (0,4)  |           2 |       2306 |     24 |        |       | x0400000009736974     | x04000000        sit
  5 |   8000 |        1 |     33 |   7049 |      0 |        0 | (0,5)  |           2 |       2306 |     24 |        |       | x050000000b616d6574   | x05000000x0Bamet
(5 rows)

That makes a bit more sense, but not much more. For PostgreSQL, the entire t_data field is the data from each column combined. And since the table structure is known, there is no separator required. Let’s have a look at the table structure again:

  1. id INT
  2. data TEXT

Integers are 4 bytes wide, which means we need to look at the first 4 bytes:

  • 01000000 -> 01 00 00 00
  • 02000000 -> 02 00 00 00
  • 03000000 -> 03 00 00 00
  • 04000000 -> 04 00 00 00

This system is little-endian, which means that the least significant byte is stored at the smallest (first) address. These 4 numbers are the primary key in the id field, expanded to 32 bit (4 bytes), because it’s an integer.

But what about this part here: «rLorem«? That’s a bit more complicated.

It’s a text field (variable length). The text in PostgreSQL is a C struct: a length field vl_len, which is — usually — 4 bytes long. And a data field vl_dat, which can hold up to 1 GB of data. Specifying the length allows PostgreSQL to store arbitrary data, including the famous 00 in C. However it’s more complicated than that, and vl_len holds more information. We need to look at this field in a bit representation:

oid=# SELECT *,
oid-#        encode(t_data, 'escape') AS decoded,
oid-#        ('x' || encode(SUBSTR(t_data, 5, 1), 'hex'))::BIT(8) AS bits
oid-#   FROM heap_page_items(get_raw_page('show_ctid', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |         t_data         |           decoded           |   bits   
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------------------+-----------------------------+----------
  1 |   8152 |        1 |     34 |   7049 |      0 |        0 | (0,1)  |           2 |       2306 |     24 |        |       | x010000000d4c6f72656d | x01000000rLorem     | 00001101
  2 |   8112 |        1 |     34 |   7049 |      0 |        0 | (0,2)  |           2 |       2306 |     24 |        |       | x020000000d697073756d | x02000000ripsum     | 00001101
  3 |   8072 |        1 |     34 |   7049 |      0 |        0 | (0,3)  |           2 |       2306 |     24 |        |       | x030000000d646f6c6f72 | x03000000rdolor     | 00001101
  4 |   8040 |        1 |     32 |   7049 |      0 |        0 | (0,4)  |           2 |       2306 |     24 |        |       | x0400000009736974     | x04000000        sit | 00001001
  5 |   8000 |        1 |     33 |   7049 |      0 |        0 | (0,5)  |           2 |       2306 |     24 |        |       | x050000000b616d6574   | x05000000x0Bamet    | 00001011
(5 rows)

When the lowest order bit is set (on little-endian), the length field is only the remaining 7 bits, not the entire 4 bytes. It also means that the remaining data is up to 127 bytes long (2^7 — 1 = 127), the total length includes the byte for the length. This is a shortcut to store short values (up to 127 bytes) more efficiently.

If the lowest bit is not set, the second lowest bit is used to store more information. This leaves 30 bits (4 bytes = 32 bits, minus 2 bits status) for the length information, or 2^30 — 1 = 1073741823 bytes for uncompressed data. Toast data can be compressed, and can therefore store more than roughly the 1 GB.

In our case, the lowest bit is set for all 5 rows, and we need to look at the remaining bits:

  1. 0000110 -> 6
  2. 0000110 -> 6
  3. 0000110 -> 6
  4. 0000100 -> 4
  5. 0000101 -> 5

6 bytes («Lorem» = 5 bytes, plus length byte), 4 bytes («sit» = 3 bytes, plus length byte) and 5 bytes («Bamet» = 4 bytes, plus length byte).

Shorter values will always go into the one byte form, to save space. Decoding of this information is usually done by C macros.

Back to xmin and xmax

Earlier we looked into the show_xmin_xmax table, updated and deleted some data, but could not verify that the deleted data is still in the table. pageinspect can help with that as well:

oid=# SELECT *,
oid-#        encode(t_data, 'escape') AS decoded
oid-#   FROM heap_page_items(get_raw_page('show_xmin_xmax', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |   t_data   |     decoded      
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------+------------------
  1 |   8160 |        1 |     28 |   7058 |   7059 |        0 | (0,3)  |        8193 |       1280 |     24 |        |       | x01000000 | x01000000
  2 |   8128 |        1 |     28 |   7058 |   7060 |        0 | (0,2)  |        8193 |        256 |     24 |        |       | x02000000 | x02000000
  3 |   8096 |        1 |     28 |   7059 |      0 |        0 | (0,3)  |           1 |      10496 |     24 |        |       | x03000000 | x03000000
(3 rows)

We see 3 entries in the table:

  • The deleted entry (id = 1) is at the first position, and xmax is 7059
  • The second entry is id = 2, also deleted, by transaction 7060
  • The third entry is the result of the UPDATE operation, which marked the id = 1 row as deleted, and at the same time inserted a new row with id = 3, this operation has xmin = 7059

Let’s run a VACUUM on the table:

oid=# VACUUM show_xmin_xmax;
VACUUM

And look at the table again:

oid=# SELECT *,
       encode(t_data, 'escape') AS decoded
  FROM heap_page_items(get_raw_page('show_xmin_xmax', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |   t_data   |     decoded      
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------+------------------
  1 |      0 |        0 |      0 |        |        |          |        |             |            |        |        |       |            | 
  2 |      0 |        0 |      0 |        |        |          |        |             |            |        |        |       |            | 
  3 |   8160 |        1 |     28 |   7059 |      0 |        0 | (0,3)  |           1 |      10496 |     24 |        |       | x03000000 | x03000000
(3 rows)

The first two entries are gone, the space is empty and can be re-used by future DML operations. However PostgreSQL does not remove the space, or give it back to the operating system. Let’s insert a new row:

oid=# BEGIN;
BEGIN
oid=# SELECT txid_current();
 txid_current 
--------------
         7061
(1 row)

oid=# INSERT INTO show_xmin_xmax VALUES (5);
INSERT 0 1
oid=# SELECT xmin, xmax, id FROM show_xmin_xmax ORDER BY id;
 xmin | xmax | id 
------+------+----
 7059 |    0 |  3
 7061 |    0 |  5
(2 rows)

oid=# COMMIT;
COMMIT

Just one row, Vasili.

oid=# SELECT *,
       encode(t_data, 'escape') AS decoded
  FROM heap_page_items(get_raw_page('show_xmin_xmax', 0));
 lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid |   t_data   |     decoded      
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------+------------------
  1 |   8128 |        1 |     28 |   7061 |      0 |        0 | (0,1)  |           1 |       2048 |     24 |        |       | x05000000 | x05000000
  2 |      0 |        0 |      0 |        |        |          |        |             |            |        |        |       |            | 
  3 |   8160 |        1 |     28 |   7059 |      0 |        0 | (0,3)  |           1 |      10496 |     24 |        |       | x03000000 | x03000000
(3 rows)

The database did not append this new row at the end of the page, or wrote it into another page. Instead it re-used the available space, and wrote it into ctid = (0, 1).

oid

The last of the columns is oid. These days, having an oid for rows is no longer supported. In a more recent version of PostgreSQL, trying to add an OID field raises an error:
 

oid=# CREATE TABLE with_oid () WITH (OIDS=TRUE);
ERROR:  tables declared WITH OIDS are not supported

In versions up to 11, this was still supported, but not encouraged. What it means is that every table with OIDS=TRUE has an additional column named «oid«, which is populated by a different transaction id — increasing the need to run VACUUM to prevent a Transaction ID Wraparound.

Summary

Every PostgreSQL table has a couple additional columns, which are hidden by default, but can be shown when included in a query. Knowing the details of each column is helpful, as it gives very good insight into the table structure and the way PostgreSQL organizes the data.



Я смотрю на создание таблицы PostgreSQL и я наткнулся на это:

CREATE TABLE (
...
) WITH ( OIDS = FALSE );

Я прочитал документацию, предоставленную postgres, и я знаю концепцию идентификатора объекта из ООП, но все же я не понимаю,

  • почему такой идентификатор был бы полезен в базе данных?
  • сделать запросы короче?
  • когда он должен быть использован?


4476  


3  

3 ответов:

OIDs в основном дает вам встроенный, глобально уникальный идентификатор для каждой строки, содержащейся в системном столбце (В отличие от столбца пользовательского пространства). Это удобно для таблиц, где у вас нет первичного ключа, есть повторяющиеся строки и т. д. Например, если у вас есть таблица с двумя одинаковыми строками, и вы хотите удалить самую старую из них, вы можете сделать это с помощью столбца oid.

по моему опыту, эта функция обычно не используется в большинстве приложений с поддержкой postgres (вероятно, в отчасти потому, что они нестандартны), и их использование по существу устарело:

в PostgreSQL 8.1 default_with_oids это
выключено по умолчанию; в предыдущих версиях
PostgreSQL, он был включен по умолчанию.

использование идентификаторов в таблицах пользователя
считается устаревшим, поэтому большинство
установки должны оставить это
переменная отключена. Приложения, которые
требует настройки для конкретной таблицы
следует оговорить с OID-ы при создании
таблица. Этот переменная может быть
включено для совместимости со старыми
приложения, которые не следуют этому
поведение.

OID все еще используются для Postgres С большой объекты (хотя некоторые люди утверждают, что большие объекты обычно не полезны в любом случае). Они также широко используются системные таблицы. Они используются, например,тост который хранит больше чем 8KB BYTEA (etc.) с отдельной зоной хранения (прозрачно), который используется по умолчанию все таблицы. Их непосредственное использование связано с» обычными » пользовательскими таблицами это в основном не рекомендуется.

тип oid в настоящее время реализуется как беззнаковое четырехбайтовое целое число. Поэтому он недостаточно велик, чтобы обеспечить уникальность всей базы данных в больших базах данных или даже в больших отдельных таблицах. Таким образом, использование столбца OID созданной пользователем таблицы в качестве первичного ключа не рекомендуется. Oid лучше всего использовать только для ссылок на системные таблицы.

по-видимому, последовательность OID «делает» обертку, если она превышает 4B 6. Так что по сути это глобальный счетчик, который можно обернуть. Если он обертывается, некоторое замедление может начаться, когда он используется и «ищет» уникальные значения и т. д.

Смотрите также https://wiki.postgresql.org/wiki/FAQ#What_is_an_OID.3F

удалить все идентификаторы из таблиц базы данных, вы можете использовать этот скрипт в Linux:

во-первых, войдите в систему как суперпользователь PostgreSQL:

sudo su postgres

теперь запустите этот скрипт, изменив YOUR_DATABASE_NAME с именем базы данных:

for tbl in `psql -qAt -c "select schemaname || '.' || tablename from pg_tables WHERE schemaname <> 'pg_catalog' AND schemaname <> 'information_schema';" YOUR_DATABASE_NAME` ; do  psql -c "alter table $tbl SET WITHOUT OIDS" YOUR_DATABASE_NAME ; done

я использовал этот скрипт, чтобы удалить все мои OID, так как Npgsql 3.0 не работает с этим, и это больше не важно для PostgreSQL.

Понравилась статья? Поделить с друзьями:
  • Error t shirt
  • Error system was not declared in this scope
  • Error system service exception
  • Error system reflection targetinvocationexception рыбалка
  • Error system reflection targetinvocationexception как исправить