Tuesday, 22 November 2011

Auto-start of an Oracle database

The traditional way to run/stop automatically an Oracle RDBMS is to call appropriately the script dbstart/dbshut. This script takes one optional parameter, which is an ORACLE_HOME of a listener to run. The following script does the job and should be configured to run at the proper runlevels (usually start on 3, shut down on 0 and 6).
Of course one needs to keep oratab up to date in required configuration.


#!/bin/bash

## date : 2011-11-22
## desc : starts|stops all the instances with enabled autostart named in oratab
## desc : usually to be used on dev/test environments
## os : Linux, possibly Solaris, other may need improvements
## loc : /etc/init.d

CMD=$1
LOG='log/oracle_db.log'
su -l oracle -c 'if [ ! -d $HOME/log ]; then mkdir -p $HOME/log; fi;'
case $CMD in
start)
su -l oracle -c '$ORACLE_HOME/bin/dbstart $ORACLE_HOME &> $HOME/$LOG'
;;
stop)
su -l oracle -c '$ORACLE_HOME/bin/dbshut $ORACLE_HOME &> $HOME/$LOG'
;;
*)
echo "Usage: $0 start|stop"
;;
esac
if [ $? -ne 0 ]; then
echo 'Command failed. More details in /log/oracle_db.log'
fi

exit 0


Of course Windows does the autostart in a different way - one needs to set proper flag/s in the registry.

Shrinking temporary tablespaces in an Oracle database

This new in 11g feature is very similar to resizing temporary tablespace files. The main difference is that while resizing the RDBMS is able to cut off only not allocated space, while shrinking additionally is able to deallocate space, which is free and a subject to reuse.

So there is no analogy to shrinking tables for example. There is no data movement. If in the area of a cut there exists an object used at the moment, shrinking is of course able to return back only the space between the end of a last such object and the end of a file.

Saturday, 22 October 2011

GLOBAL_NAME and db links

Once we created a database for migration from other older system with some domain_name. We set a db_domain in order to be compliant with other databases we managed. However the database to be migrated worked without such setting. Thus we hit a problem when creating database links, as those new were created with some suffix even provided with double quotes and in result some code did not want to compile.

What is behind the scene?
It seems at the database creation the GLOBAL_NAME setting for this database is created as well based on db_name and db_domain settings. Any changes to those base parameter are not reflected further in this GLOBAL_NAME value. And that is GLOBAL_NAME which influence database links name - its domain part is added to every database link.

How to deal with it?
http://dbatips4u.blogspot.com/2009/04/tip21-db-link-name-suffix.html provides the details.
I add here small note to it - I was especially interested in cancelling the global_name domain part at all, which is less trivial than just setting it to something new as the simple ALTER DATABASE RENAME GLOBAL_NAME TO "{db_name}"; does not work as planned (the domain part stays in place). I followed the notes from the link above. I run UPDATE GLOBAL_NAME SET GLOBAL_NAME='{db_name}'; but even though the GLOBAL_NAME content has changed, still db links' names were creates with suffix I did not want.
The important part was a database restart, after which all started work as planned.
Btw. our GLOBAL_NAMES was set to FALSE, which is different from the settings presented in the tip from the link above - may be that is the clue, why I needed to restart.

Wednesday, 21 September 2011

10g to 11g upgrade

I used to think that pre-upgrade tools (ie. utlu112i.sql) are clearly for reporting purposes. I mean this script simply displays what one needs to fix in order to prepare a database to upgrade process.
Yesterday showed I was wrong.
So here is what You should not do at home ;-)
I started with direct setting of compatible parameter to 11.2.0.0.0 (which in turn was a very, very bad move). Because we upgraded quite a bunch of databases I felt quite sure. Another thing was we prepared a procedure for upgrade (based on documents from Metalink, but shortened, as we do not need to go through all the steps) and there the pre-upgrade tool was missing.
So I startup in upgrade mode and after a while I get an error that there is unknown identifier TZ_VERSION in table REGISTRY$DATABASE. The hint in script was simply to run utlu112i.sql - it became clear that there are some changes made by utlu112i.sql to the database structure.
Due to setting compatible parameter to 11.2.0.0.0 I was unable to start this database again as 10g version, so can not run utlu112i.sql. @#$$$%!
Then I tried to add a column to the problematic table. The command was successful, but the structure showed still as old due to running in upgrade mode and apparently in a different edition.
Recreating the controlfiles based on the database structure, which I still was able to query, was successful, but it is not enough because data files were changed as well (probably only headers, but this solution path was thus closed).
What I did finally was a simple ugly hack.
I edited the file $ORACLE_HOME/rdbms/admin/catupstr.sql and changed queries to the REGISTRY$DATABASE replacing TZ_VERSION calls with a value ('14' in my case). Then I just started catupgrd.sql again.

There were some issues with the SI_INFORMATN_SCHEMA schema, but not sure if this has anything to do with problems with pre-upgrade tool and as this is a test environment, it is not so clean as the production (some not compiling code pieces, etc). Eventually the problem was solved.

Concluding:
  1. do not change compatible parameter to newer when upgrading - do it sometime afterwards
  2. always run pre-upgrade tool

Thursday, 19 March 2009

Hints in Oracle

Funny thing... I used to add any hints within block comments i.e. /*+ index(whatever) */, so never thought that line comment -- make it.
Does it? Sure it does --+ index(whatever). The only thing is one can not continue a SQL statement with it on the same line.

Wednesday, 23 January 2008

Kernel settings on Solaris 10

Actually the main change is setting of shmsys:shminfo_shmmax by project.max-shm-memory in /etc/project.
One can also add noexec_user_stack=1 to /etc/system, but it requires further reboot of a system. Other modifications can be not performed.

Monday, 15 October 2007

Script for parsing *.ora files

The script below is able to parse *.ora files - mainly tnsnames.ora or listener.ora, though it should properly deal with other files with this Oracle syntax (complex values build by nested brackets, simple ones as pairs of key and value).

It is not complete (probably list of keywords in @keywords does not exhaust all possibilities, but it is enough to rebuild it with new entries).
Data::Dumper module is not a must - it is used only for debugging purposes and can be removed.

Lately I have got a question what licensing I would apply to this script and here some rules:
  • the script is free of charge
  • I would like to be still presented as an author ;-)
  • one can not distribute and charge for it (which does not mean it can not be added as a kind of "free" bonus to something commercial, but it must be stated so somewhere)
  • one may modify the script along to the needs - it would be nice to return changes to me assuming they have a general character (so are not bound to some local environment)

The script in more edible form

#!/usr/bin/perl -w
## ----------------------------------------------------------------
## $Id: ora_tns_parser.pl,v 1.2 2012/11/22 23:16:00 rems Exp $
## @action: parses *.ora files
## $Author: Remigiusz Sokolowski <rems@wp.pl> $
## $Date: 2007/10/15 10:50:15 $
## $Revision: 1.2 $
##
## Opis
## $Log: ora_tns_parser.pl,v $
## Revision 1.1  2007/10/15 10:50:15  rems
## tool for parsing *.ora files (mainly tnsnames or listener)
##
## parses provided *.ora file and returns pairs in the form
## key:value
## ALL - default; all pairs
## CUSTOM - alias/listener pairs (i.e. no parameters)
## LISTENERS_AND_PASSWORDS - pairs listener:encoded password (relevant only for listener.ora)
## NAMES - only keys - alias/listener names
##
## Usage
## ===================
## ./ora_tns_parser.pl <format> <path>
##
## Revision 1.2 2012/11/22 23:16:00 rems
## fix for bugged behavior when specific *.ora format in use (one line entry)
## not tested yet completely 
## ----------------------------------------------------------------

## arguments
my $fmt = shift;
my $fname = shift;

## modules and constant variables
my %formats = (
'ALL'    => 1, #default
'CUSTOM' => 1,
'LISTENERS_AND_PASSWORDS' => 1,
'NAMES'  => 1
);

my @keywords = (
'INBOUND_CONNECT_TIMEOUT',
'LOG_DIRECTORY',
'LOG_FILE',
'LOGGING',
'PASSWORDS',
'SAVE_CONFIG_ON_STOP',
'SID_LIST',
'TRACE_DIRECTORY',
'TRACE_FILE',
'TRACE_LEVEL'
);

use strict;
use warnings;
use Data::Dumper;

## functions
sub trim {
my $string = shift;
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return $string;
}

## counting instances of the provided x char in string tval
sub _count_chars {
my $x = shift;
my $tval = shift;
my @chars = split(//, $tval);
my $ch;
my $counter = 0;
foreach $ch (@chars) {
if ($ch eq $x) {
$counter++;
}
}
return $counter;
}

## whole algorithm depends on counting brackets
sub parse_tns_addr {
my $lines = shift;
my $i = shift;
my $tval = shift;
my $level = shift;

my $numL = _count_chars('(', $lines->[$i]);
my $numP = _count_chars(')', $lines->[$i]);
$level = $level + $numL - $numP;

if ($level <= 0) {
return ($i, $tval, 0);
} else {
$tval = $tval . trim($lines->[++$i]);
($i, $tval) = parse_tns_addr($lines, $i, $tval, $level);
}
}

## execution
my @lines;
my $line;
open(FH, "< $fname");
while (<FH>) {
chomp;
$line = trim($_);
if ($line !~ m/^#/ && $line !~ m/^\s*$/) {
push(@lines, $_);
}
}
close FH;

my $i=0;
my $pname = '';
my $pval = '';
my %params;

my @tmp;
my $trimmed = '';
for ($i=0; $i<@lines; $i++) {
## if sign '=' is not in line, it must be in the next one
if ($lines[$i] !~ m/=/) {
$lines[$i+1] = $lines[$i].$lines[$i+1];
$i++;
}
if ($lines[$i] =~ m/=/) {
## if in line there is '=', we split at first equality sign
if ((my $pos = index($lines[$i], "=")) >= 0) {
$tmp[0] = substr($lines[$i], 0, $pos);
$tmp[1] = substr($lines[$i], $pos+1);
} else {
$tmp[0] = $lines[$i];
}
$pname = trim($tmp[0]);

$trimmed = defined $tmp[1] ? trim($tmp[1]) : undef;
## if first char is '(', complex entry
if (@tmp>1 && substr($trimmed,0,1) eq '(') {
($i, $pval) = parse_tns_addr(\@lines, $i, $trimmed, 0);

## if not and entry has some not empty value, simple entry
} elsif (@tmp>1 && $trimmed ne '') {
$pval = $trimmed;

## it is possible, that value is in next line/s
} else {
$i++;
$trimmed = trim($lines[$i]);
if (substr($trimmed,0,1) ne '(') {
$pval = $trimmed;
} else {
my $level;
($i, $pval) = parse_tns_addr(\@lines, $i, $trimmed, 0);
}
}
$params{$pname} = $pval;
} else {
## if not, and we have no empty lines, probably wrong syntax in file
die "probably wrong syntax at line $i!"
}
@tmp = ();
$pname = '';
$pval  = '';
}

## for debugging purposes
#print "++++++++++++++++++++++++++\n";
#print Dumper(%params);
#print "++++++++++++++++++++++++++\n";

## we parse our list to throw away not needed entries
my $keyword;

## trimming output according to format
my $flg_del = 0;
if (defined $formats{$fmt} && $fmt ne 'ALL') {
foreach $pname (keys %params) {
foreach $keyword (@keywords) {
if (substr($pname, 0, length $keyword) eq $keyword) {
$flg_del = 1;
if ($fmt eq 'LISTENERS_AND_PASSWORDS' && $keyword eq 'PASSWORDS') {
$params{substr($pname, 1 + length 'PASSWORDS')} = $params{$pname};
}
}
}
if ($flg_del == 1) {
delete($params{$pname});
$flg_del = 0;
} elsif ($fmt eq 'LISTENERS_AND_PASSWORDS' && substr($params{$pname}, 0, 1) eq '(') {
## a little ugly hack, assuming that entries without keywords
## with values starting with "(" are certainly custom names
## i.e. listener names
$params{$pname} = '';
}
}
}

## output
my $format = (defined $formats{$fmt} && $fmt eq 'NAMES') ? "%s\n" : "%s:%s\n";
foreach $pname (keys %params) {
printf $format, $pname, $params{$pname};
}