DBD::DB2: why does a `$dbh->disconnect` cause gaps in an autoincrement column? - perl

Why does the $dbh->disconnect cause gaps in the auto-increment column?
use DBI;
my $db = 'MYDB2';
my $table = 'SCHEMA.TABLE';
my $user = 'user';
my $pass = 'passwd';
my $dsn = "dbi:DB2:$db";
my $dbh = DBI->connect( $dsn, $user, $pass );
$dbh->do( "DROP TABLE IF EXISTS $table" );
$dbh->do( "CREATE TABLE $table (ID INT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, NAME CHAR(3))" );
my $sth = $dbh->prepare( "INSERT INTO $table (NAME) VALUES(?)" );
$sth->execute( 'aaa' );
$sth->execute( 'bbb' );
$sth = $dbh->prepare( "SELECT * FROM $table" );
$sth->execute();
$sth->dump_results;
$sth->finish;
$dbh->disconnect;
$dbh = DBI->connect( $dsn, $user, $pass );
$sth = $dbh->prepare( "INSERT INTO $table (NAME) VALUES(?)" );
$sth->execute( 'ccc' );
$sth->execute( 'ddd' );
$sth = $dbh->prepare( "SELECT * FROM $table" );
$sth->execute();
$sth->dump_results;
'1', 'aaa'
'2', 'bbb'
2 rows
'1', 'aaa'
'2', 'bbb'
'21', 'ccc'
'22', 'ddd'
4 rows
Without disconnect the auto-increment column is created without gaps:
use DBI;
my $db = 'MYDB2';
my $table = 'SCHEMA.TABLE';
my $user = 'user';
my $pass = 'passwd';
my $dsn = "dbi:DB2:$db";
my $dbh = DBI->connect( $dsn, $user, $pass );
$dbh->do( "DROP TABLE IF EXISTS $table" );
$dbh->do( "CREATE TABLE $table (ID INT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, NAME CHAR(3))" );
my $sth = $dbh->prepare( "INSERT INTO $table (NAME) VALUES(?)" );
$sth->execute( 'aaa' );
$sth->execute( 'bbb' );
$sth = $dbh->prepare( "SELECT * FROM $table" );
$sth->execute();
$sth->dump_results;
$sth->finish;
#$dbh->disconnect;
$dbh = DBI->connect( $dsn, $user, $pass );
$sth = $dbh->prepare( "INSERT INTO $table (NAME) VALUES(?)" );
$sth->execute( 'ccc' );
$sth->execute( 'ddd' );
$sth = $dbh->prepare( "SELECT * FROM $table" );
$sth->execute();
$sth->dump_results;
'1', 'aaa'
'2', 'bbb'
2 rows
'1', 'aaa'
'2', 'bbb'
'3', 'ccc'
'4', 'ddd'
4 rows

This is working as designed, IDENTITY columns GENERATED ALWAYS are not guaranteed to be consecutive and gaps should be expected.
This is nothing to do with Perl or DBD:DB2, it is just how Db2 implements identity columns that are GENERATED ALWAYS. Db2 internally maintains a small cache of values for identity values for such columns per connection, and you can get gaps after ROLLBACK of a transaction that consumed a value, or after a crash or abnormal termination, or if other apps (other connections) are inserting values into the same identity column, or the increment/decrement value is not 1, or database deactivation.
Although you can use the "NO CACHE" option when specifying your identity column, or specify a lower cache value, these options are undesirable for performance / concurrency reasons.
You can also reduce database deactivation (but not eliminate it) by arranging to have the database explicitly activated before the first connection happens ( db2 activate database $dbname) . This will ensure the database does not automatically deactivate itself when the last connection does the disconnect , and the cache of pre-allocated numbers for the identity column(s) will not be lost in this case.
If you want to enforce zero gaps, you have to use a different technique, which usually has performance implications for high-insert-frequency apps and/or scalability/concurrency challenges.

You can change that my using the CACHE option for identity columns. Basically, for performance reasons, a number of identities is cached and used, so that syncing is avoided. You can reduce the gap but risk a reduction in performance.

The reason is, that the database is deactivated highly likely upon your application session disconnection, since it's the only application working with the database.
When a database is deactivated, all unused sequence cache values are lost.
You may avoid implicit database activation / deactivation with the ACTIVATE DATABASE command. But it doesn't help you to avoid gaps in case of rollbacks and the instance restart.

Related

PDO postgresql last inserted id return 'false' or -1

I have code
$dbh = new PDO("pgsql:host=localhost; port=5432; dbname=gpd; user=postgres; password=postgres");
$sth = $dbh->prepare("INSERT INTO res (res_name, res_order) VALUES ('London', 1)");
$sth->execute();
$id = $dbh->lastInsertId();
echo $id;
Record insert, but $id is empty.
I try execure query
INSERT INTO res (res_name, res_order) VALUES ('London', 1) RETURNING id
and received a value "-1"
Going home, I myself understood what must to do
$dbh = new PDO("pgsql:host=localhost; port=5432; dbname=gpd; user=postgres; password=postgres");
$sth = $dbh->prepare("INSERT INTO res (res_name, res_order) VALUES ('London', 1)");
$sth->execute();
$result = $sth->fetch(PDO::FETCH_ASSOC);
echo "ID - ". $result["id"];
It's simple

Populating missing rows in postgresql

I have written a CGI script in perl. I am receiving POST request to that script from two different locations simultaneously.
From POST request i am populating five different tables. I get 500 rows in each POST request and then i populate each table with 100 rows.
My issue is that after some time one or two of the table start showing less number of rows. Both of these tables store ip in one of their column. Every time only these two tables are the one which is missing data which is weird for me. I am not able to understand why this is happening.
Although i receive something in postgresql logs and number of such logs are same as the number of rows missing from table.
UTC STATEMENT: insert into post (ip, count, location, source_server) values ($1, $2, $3, $4)
UTC STATEMENT: insert into post (ip, count, location, source_server) values ($1, $2, $3, $4)
UTC STATEMENT: insert into post (ip, count, location, source_server) values ($1, $2, $3, $4)
This is my CGI code.
use strict;
use warnings;
use CGI;
use DBI;
use JSON;
#Taking parameters passed as post request
my $conn = CGI->new;
my $data = $conn->param('POSTDATA');
#Details regarding database
my $host = 'host';
my $user = 'user';
my $password = 'password';
my $database = 'db';
my ( $location, $table, $entry );
#Creating connection to DB
my $dsn = "dbi:Pg:dbname=$database;host=$host";
my $dbh
= DBI->connect( $dsn, $user, $password,
{ RaiseError => 0, PrintError => 0 } )
or die("Connection to DB failed $!\n");
#Decoding json received in post request to perl hash
$data = decode_json $data;
my ($source_server) = keys %$data;
my $location = 'XX';
my $main_data = $$data{$source_server};
my #db = qw /a b c d e/;
my %mapping = (
'a' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
'e' => 'E'
);
foreach (#$main_data) {
my $hash = $_;
#Deciding which table data will be pushed into
$table = shift #db;
$entry = $mapping{$table};
my $sth = $dbh->prepare(
"insert into $table ($entry, count, location, source_server) values (?, ?, ?, ?)");
while ( ( my $key, my $value ) = each %$hash ) {
$sth->execute( $key, $value, $location, $source_server );
}
$sth->finish;
}
#Sending 200 status code back to client
print "Status: 200\n\n";
Data is passed to my CGI script in JSON as {KEY : [{HASH},{HASH},{HASH},{HASH},{HASH}]}. Second and fifth hash contains data for tables which are missing it after some time.
Every hash will have 100 key:value pairs thus making total 500 entries.
Well after simbabque comment above i enabled RaiseError and PrintError. After this i am able to get error in server error logs. Error logs indicate that because of simultaneous insertion of rows coming from multiple places i am getting primary key violation which was causing this strange behaviour.
I tweaked my primary key and all is well :)

Fetching one single row from database using Perl

I am trying to run a simple database connection with Perl using postgreSQL
use DBI;
$database = "postgres";
$user = "postgres";
$password = "admin";
my $dbh = DBI->connect( "dbi:Pg:dbname=$database"
, $user
, $password
)
or die "Can't Connect to database: $DBI::errstr\n";
# get data from the URL string
my $firstname = "haroon";
my $lastname ="ash";
my $age = 24;
# insert the data into the database
my $query = "INSERT INTO people (firstname, lastname, age)
VALUES ('$firstname','$lastname', '$age')";
$dbh->do($query);
# get the ID of the inserted person
$query = "SELECT MAX(id) FROM people";
my $sth = $dbh->prepare($query);
my $rv =$sth->execute;
if($rv < 0){
print $DBI::errstr;
}
else {
my $row = $sth->fetchrow_hashref;
my $person_id = $row->{'max'};
print $firstname, $lastname
. "was successfully inserted at position "
. $person_id;
}
I am trying to print the person id which i had entered latest. But my $person_id = $row->{'max'} seems to give me the correct answer instead of my $person_id = $row->{'id'};. I am not understanding why is that.
You might want to set column alias for query,
$query = "SELECT MAX(id) AS id FROM people";
as postgres is putting his own alias for you, and that is max.
If all you want is last inserted id, you can
my $query = "INSERT INTO people (firstname, lastname, age)
VALUES (?,?,?)
returning id
";
and fetch query as you would do with select. (check pg docs)
You can use the RETURNING keyword to return the id associated with the row you just inserted:
my $query = '
INSERT INTO people (firstname, lastname, age)
VALUES ($1, $2, $3)
RETURNING id';
my $sth = $dbh->prepare($query);
$sth->execute($firstname, $lastname, $age);
my $rv = $sth->fetchrow_hashref();
printf "%s, %s was successfully inserted at position %d\n",
$firstname, $lastname, $rv->{id};
You did not enter a PERSON_ID in your insert statement.
Instead it seems the ID field was populated by the DB, an auto increment value during insert, it seems.
Without knowing the table definition (and potential insert trigger on the table) it is hard/impossible to give you a better answer.

DBD::SQLite: Insert or Update - Question

While writing my question (howto: insert a new entry and if it exists already, update it) I found some answers in the Related Questions:
$sql = "INSERT OR REPLACE INTO $table ( Id, Name, Rating ) VALUES( ?, ?, ? )";
$sth_rating = $dbh->prepare( $sql );
$sth_rating->execute( $id, $name, $rating );
.
$sql = "INSERT OR IGNORE INTO $table ( Id, Name, Rating ) VALUES ( ?, ?, ? )";
$sth_rating = $dbh->prepare( $sql );
$sth_rating->execute( $id, $name, $rating );
$sql = "UPDATE $table SET Rating = ? WHERE Id = ?";
$sth_rating = $dbh->prepare( $sql );
$sth_rating->execute( $rating, $id );
Is the second method more safe then the first one?
The second method is less safe because it is non-atomic. In other words, it happens in more than one step. Consider two processes both updating the same data. Time is moving down.
Process 1 Process 2
INSERT OR IGNORE...
INSERT OR IGNORE...
UPDATE...
UPDATE...
Process 1 starts first, but Process 2 sneaks in and does its update between. Process 1 then blows right over Process 2's update.
This isn't so bad in this particular situation, both processes are going to blow over each other no matter what, but you can easily get yourself into trouble by extending your technique.
(Unless I misunderstood the question and what you want is an upsert)

DBI: alter table - question

#!/usr/bin/env perl
use warnings;
use 5.012;
use DBI;
my $dsn = "DBI:Proxy:hostname=horst;port=2000;dsn=DBI:ODBC:db1.mdb";
my $dbh = DBI->connect( $dsn, undef, undef ) or die $DBI::errstr;
$dbh->{RaiseError} = 1;
$dbh->{PrintError} = 0;
my $my_table = 'my_table';
eval{ $dbh->do( "DROP TABLE $my_table" ) };
$dbh->do( "CREATE TABLE $my_table" );
my $ref = [ qw( 1 2 ) ];
for my $col ( 'col_1', 'col_2', 'col_3' ) {
my $add = "$col INT";
$dbh->do( "ALTER TABLE $my_table ADD $add" );
my $sql = "INSERT INTO $my_table ( $col ) VALUES( ? )";
my $sth = $dbh->prepare( $sql );
$sth->bind_param_array( 1, $ref );
$sth->execute_array( { ArrayTupleStatus => \my #tuple_status } );
}
my $sth = $dbh->prepare( "SELECT * FROM $my_table" );
$sth->execute();
$sth->dump_results();
$dbh->disconnect;
This script outputs:
'1', undef, undef
'2', undef, undef
undef, '1', undef
undef, '2', undef
undef, undef, '1'
undef, undef, '2'
6 rows
How do I have to change this script to get this output:
'1', '1', '1'
'2', '2', '2'
2 rows
Do this in two steps:
Create the 3 columns
insert data in them
You prepare a SQL statement 3 times and execute twice for values 1,2 so you get 6 rows. I don't know how to answer your question of how do you change it to get 2 rows since we've no idea what you are trying to achieve. Without knowing what you are trying to achieve I'd be guessing but the following results in the output you wanted:
my $ref = [ qw( 1 2 ) ];
for my $col ( 'col_1', 'col_2', 'col_3' ) {
my $add = "$col INT";
$dbh->do( "ALTER TABLE $my_table ADD $add" );
}
$sql = "INSERT INTO $my_table ( col_1, col_2, col_3 ) VALUES( ?,?,? )";
my $sth = $dbh->prepare( $sql );
$sth->bind_param_array( 1, $ref );
$sth->bind_param_array( 2, $ref );
$sth->bind_param_array( 3, $ref );
$sth->execute_array( { ArrayTupleStatus => \my #tuple_status } );