mercredi 22 juin 2011

Oracle Services Management



The Oracle Services are a wonderful tool to administer applications within an Oracle instance :
Resource Manager / Control Application access / RAC and so on …

Remember these limits to avoid « maximum number of services exceeded »,
we’ll come back to them later.

Maximum number of Oracle Services ( minus 3 for default background Services )
10GR2  115
11GR2  150
12CR1 1024


0] Creation/Drop

0.1 Creation

Call under SYSTEM user the CREATE_service.sql with Service name as argument


cat CREATE_service.sql
---CREATE_service.sql
-- P1 : Nom du service
def serv_name = &1
Begin
  DBMS_SERVICE.CREATE_SERVICE(
     service_name => '&serv_name',
     network_name => '&serv_name',
     goal => DBMS_SERVICE.GOAL_NONE,
     failover_method => DBMS_SERVICE.FAILOVER_METHOD_NONE,
     failover_type => NULL,
     failover_retries => NULL,
     failover_delay => NULL,
     dtp => NULL,
     aq_ha_notifications => NULL,
     clb_goal => NULL);

End;
/
undefine serv_name

exit


Then Call under SYSTEM user the START_service.sql with Service name & ORACLE_SID as arguments


cat START_service.sql
-- START_service.sql
-- P1 : Nom du service
-- P2 : Nom de l'instance => SID
def serv_name = &1
def inst_name = &2
Begin

  DBMS_SERVICE.START_SERVICE(
     service_name => '&serv_name',
     instance_name => '&inst_name');
End;
/


Don’t forget to set this new Service as Startable next instance boot : following section.

0.2 Drop

sqlplus system/$system_psw @STOP_service.sql $1 $ORACLE_SID


$ cat STOP_service.sql
-- STOP_service.sql
-- P1 : Service Name
-- P2 : Instance name => SID
def serv_name = &1
def inst_name = &2
Begin

  DBMS_SERVICE.STOP_SERVICE(
     service_name => '&serv_name',
     instance_name => '&inst_name');
End;
/
declare service varchar2(512);
Begin
 select replace(value,', ',Chr(39)||','||Chr(39)) into service from v$parameter where name='service_names';
 execute immediate 'alter system set service_names='''||service||'''';
End;
/
undefine serv_name
undefine inst_name

exit


sqlplus system/$system_psw @DELETE_service.sql $1



$ cat DELETE_service.sql
-- DELETE_service.sql
-- P1 : Service name
def serv_name = &1
Begin

  DBMS_SERVICE.DELETE_SERVICE(
     service_name => '&serv_name');
End;
/
undefine serv_name

exit



1] Set Service start at instance bounce and the problem hitting ORA-32021: parameter value longer than 255 characters

1.0 The problem

We defined Oracle Services to access Application Data in our Databases.
The Services, once created should be set as startable at instance bounce.
Even if the Service Name is a 3-digits string, when adding a new Service, we rapidly reached the Maximum number of Characters in the SERVICE_NAMES instance parameter.


SQL> alter system set service_names='SV1,…,SVCN' ;
alter system set service_names='SV1,…,SVCN'
*
ERROR at line 1:
ORA-32021: parameter value longer than 255 characters


To correct this, 2 possibilities

-       Sol 1 :Use a trigger to start the Oracle Services
Or better :
-       Sol 2 :Build the service_names parameter with multiple strings instead of only one

1.1 Solution 1 : Trigger


Goal
Trigger to Start Oracle Services on Startup Database
Version
10gR2
Views
V$INSTANCE DBA_SERVICES
Packages
DBMS_SERVICE
Log as
SYS


CREATE OR REPLACE TRIGGER SYS.START_SERVICES AFTER STARTUP on DATABASE
declare
 nn number;
 INST_NAME varchar2(16);
begin
  execute IMMEDIATE 'select INSTANCE_NAME from v$instance' INTO INST_NAME;
  for v_loop in (select name from dba_services order by 1) LOOP
    SELECT count(1) INTO nn from v$active_services WHERE UPPER(name)=UPPER(v_loop.name);
    IF (nn = 0) then
      DBMS_SERVICE.START_SERVICE(service_name => ''||v_loop.name||'' , instance_name => ''||INST_NAME||'');
       end IF;
  end loop;
end;
/


Another version that takes care of Dataguard Standby DB, owned by SYS user :

CREATE OR REPLACE TRIGGER START_SERVICES AFTER STARTUP on DATABASE
declare
inst_name varchar2(16) ;
nn number;
begin
 select INSTANCE_NAME INTO INST_NAME from v$instance;
 execute immediate 'alter system set service_names='''||inst_name||'.cg13.fr''';
 for v_loop in (select name from dba_services where upper(name) not like '%DGB' and upper(name) not like '%DGMGRL' and upper(name) not like ''||substr(inst_name,1,4)||'%' order by 1) LOOP
   SELECT count(1) INTO nn from v$active_services WHERE UPPER(name)=UPPER(v_loop.name);
   IF (nn = 0) then
     DBMS_SERVICE.START_SERVICE(service_name => ''||v_loop.name||'');
   end IF;
 end loop;
end;
/

1.2 Solution 2 : Set service_names parameter

Build the parameter from the existing services DBA_SERVICES

select ''''||name||''',' from dba_services order by 1
and remove 3 default Services : sys$users , background & instance_name, to get :


SQL> ALTER SYSTEM SET service_names='SVC1','SVC2',…,'SVCN' ;
System altered.

SQL> select count(1) from v$services ;
  COUNT(1)
----------
        75

SQL> select count(1) from dba_services ;
  COUNT(1)
----------
        75


Mon Oct 03 11:02:30 GMT+02:00 2011ALTER SYSTEM SET service_names='SVC1',…,'SVCN' SCOPE=BOTH;

SQL> select value from v$parameter where name='service_names';

VALUE
--------------------------------------------------------------------------------
SVC1, …, SVCN


Now, adding a service to the Instance startup is a piece of cake :


set serveroutput on
declare service varchar2(512);
Begin
-- select value into service from v$parameter where name='service_names';
 select replace(value,', ',Chr(39)||','||Chr(39)) into service from v$parameter where name='service_names';
 DBMS_OUTPUT.PUT_LINE('Oracle Services : ' ||service);
 execute immediate 'alter system set service_names='''||service||'''';
End;
/


From memory

Mon Jan 28 10:57:50 GMT+01:00 2013
ALTER SYSTEM SET service_names='SVC1',…,'SVCN' SCOPE=MEMORY SID='INSTANCE';

To Spfile

Mon Jan 28 10:57:50 GMT+01:00 2013
ALTER SYSTEM SET service_names='SVC1',…,'SVCN','' SCOPE=BOTH;
Mon Jan 28 10:58:05 GMT+01:00 2013Thread 1 advanced to log sequence 961374 (LGWR switch)

SQL> select value from v$parameter where name='service_names';
VALUE
--------------------------------------------------------------------------------
SVC1, …, SVCN,

create ok start ok

set serveroutput on size 100000
Begin
  DBMS_SERVICE.START_SERVICE(
     service_name => 'SCH',
     instance_name => 'INSTANCE');
End;
/
PL/SQL procedure successfully completed.

SQL> !lsnrctl stat | grep -i auc
Service "SCH.corp.fr" has 1 instance(s).

is in v$parameter2 / is not in v$parameter

sqlplus usr/<psw>@"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=HOST)(Port=1521))(CONNECT_DATA=(service_name=auc.corp.fr)))"

1.3 Solution 2 : NEW VERSION with V$PARAMETER2

One difference between v$parameter & v$parameter2 is that the column "value" of v$parameter is VARCHAR2 (512 Byte) when the values are given by v$parameter2 with multiple rows.
It makes a big difference when talking about Service_Name for instance
we easily reach the max value of v$parameter, but still can get the full list from v$parameter2 !

-- total list of services
select name from dba_services -- has 3 more services: instance name , SYS$USERS & SYS$BACKGROUND
minus
-- in-memory services, better than v$parameter which max number of characters
select value from v$parameter2 where name='service_names'
order by 1 ;


New Script :

SET SERVEROUTPUT ON
DECLARE
CURSOR services_c IS select value from v$parameter2 where name='service_names' order by 1 ;
service varchar2(2048) ;
BEGIN
 FOR v_service IN services_c LOOP
-- DBMS_OUTPUT.PUT_LINE('svc = '||v_service.value);
   service := ''||service||''','''||v_service.value ;
   END LOOP;
   select regexp_replace(service,''',','',1,1) into service from dual ;
   select regexp_replace(service,'$','''') into service from dual ;
   -- DBMS_OUTPUT.PUT_LINE('svc = '||service);
   execute immediate 'alter system set service_names='||service ;
 END;
/

Mon Jan 28 16:59:02 GMT+01:00 2013ALTER SYSTEM SET service_names='SVC1',…,'SVCN' SCOPE=BOTH;


2] Package to manage DB access

Goal
Open or Close Database access,
based on Service Name ( or on Roles if not existing )
Version
10gR2
Views
V$ACTIVE_SERVICES DBA_ROLE_PRIVS V$SESSION
v$locked_object a , dba_objects
Packages
DBMS_SERVICE
Log as
SYSTEM

2.1 Pre-requisite

-       an application is defined as a 3 characters code
-       an Oracle Service is created for application access = the Application code
-       to locate sessions of application without an Oracle Service, we may use the application Roles named starting with the App-code.

Direct Grants on SYS objects to SYSTEM are necessary for this package to be valid, because Roles are not taken into account in PL/SQL ( 10g constraint ) :

GRANT EXECUTE ON SYS.DBMS_SERVICE TO SYSTEM;
GRANT SELECT ON  SYS.DBA_OBJECTS TO SYSTEM;
GRANT SELECT ON  SYS.DBA_ROLE_PRIVS TO SYSTEM ;
GRANT SELECT ON  SYS.V_$ACTIVE_SERVICES to system ;
GRANT SELECT ON  SYS.V_$LOCKED_OBJECT TO SYSTEM;
GRANT SELECT ON  SYS.V_$SESSION TO SYSTEM;
grant alter system to system ;

2.2 The package has the following procedures with only argument : the Application Code.

STOP_APP – Application wide, to kill every Applicative running sessions
-       stops Oracle Service if exists,
-       then kill sessions found out using Service Package or Roles

START_APP – Application wide, to restart Oracle Service if available

BEFORE_BTC – Application wide, to kill only « blocking » sessions that
-       have level 3 locks on application objects
-       may prevent batch program to run

BEFORE_BTC_ALL same – Instance wide


CREATE OR REPLACE package app_admin
as
-- 20/03/11 SCH
-- 05/01/12 validée par DC
-- app : Trigramme Application
-- exécution sous compte Exploitation ( ex: ORA_BAT )
        procedure stop_app (app in varchar2);
        procedure start_app (app in varchar2);
        procedure before_btc (app in varchar2);
        procedure before_btc_all ;
end;
/


CREATE OR REPLACE package body app_admin
as
sql_stmt  varchar2 (500);
cn integer;
nb number;
ret number;

procedure stop_app (app in varchar2)
is
-- stops Oracle Service if exists,
-- then kill sessions found out using Service Package or Roles
-- usage : execute system.app_admin.stop_app('APPLI');

-- 1/ If there is an existing Oracle Service
-- then use dbms_service package
nn number;
cursor cur is
 select 'alter system kill session '''||SID||','||SERIAL#||'''' cmd , username from v$session
 where exists
 ( select '1' from dba_role_privs where upper(granted_role) like '%'||upper(app)||'%' and grantee = username and grantee != 'SYSTEM' )
 and status != 'KILLED' order by username ;

begin

SELECT count(1) INTO nn from v$active_services WHERE UPPER(name)=UPPER(app);
IF (nn = 1) then
 dbms_output.put_line('Kill sessions using dbms_Service for application : ' ||app);
 DBMS_SERVICE.STOP_SERVICE(service_name => app);
 DBMS_SERVICE.DISCONNECT_SESSION(service_name => app,disconnect_option => DBMS_SERVICE.IMMEDIATE);

else
begin
-- 2/ use role instead
 dbms_output.put_line('Kill sessions using Role for application : ' ||app);
 nb:=0 ;
 for c_tab in cur loop
  begin
    dbms_output.put_line('Kill session username : '||c_tab.username);
 sql_stmt := c_tab.cmd;
 cn := dbms_sql.open_cursor;
 dbms_sql.parse(cn, sql_stmt,dbms_sql.native);
 ret:=dbms_sql.execute(cn);
 dbms_sql.close_cursor(cn);
 nb:=nb+1;
 exception
  when others then
   dbms_output.put_line(sqlerrm);
  end;
 end loop;
dbms_output.put_line(nb||' killed sessions');
end ;
end IF;

end stop_app;

procedure start_app (app in varchar2)
is
-- starts Service if Not already started
-- usage : execute system.app_admin.start_app('APPLI');
nn number;
begin

SELECT count(1) INTO nn from v$active_services WHERE UPPER(name)=UPPER(app);
IF (nn = 0) then
 DBMS_SERVICE.START_SERVICE(service_name => app);
end IF;

end start_app;

procedure before_btc (app in varchar2)
is
-- Kill sessions having locks on application objects
-- batch program may use a dedicated Batch Service ( SVC1 , BTC )
-- usage : execute system.app_admin.before_btc('APPLI');

cursor cur_app is
select distinct 'alter system kill session '''||SID||','||SERIAL#||'''' cmd , username , object_type , owner , object_name
from (
select b.username username, c.object_type object_type , c.owner owner , c.object_name object_name , b.sid sid, b.serial#
from v$locked_object a , v$session b , dba_objects c
where a.object_id = c.object_id and b.sid = a.session_id and a.locked_mode = '3' and owner = upper(app)
and b.service_name not in ( 'SVC1' , 'BTC') ) ;

begin
 dbms_output.put_line('Kill potential blocking sessions for application : ' ||app);
 nb:=0 ;
 for c_tab in cur_app loop
  begin
    dbms_output.put_line('Kill session username : '||c_tab.username||' , Object: '||c_tab.object_type||' '||c_tab.object_name||'');


 sql_stmt := c_tab.cmd;
 cn := dbms_sql.open_cursor;
 dbms_sql.parse(cn, sql_stmt,dbms_sql.native);
 ret:=dbms_sql.execute(cn);
 dbms_sql.close_cursor(cn);
 nb:=nb+1;
 exception
  when others then
   dbms_output.put_line(sqlerrm);
  end;
 end loop;

dbms_output.put_line(nb||' killed sessions');
end before_btc;

procedure before_btc_all
is
-- Kill sessions having blocking locks on application objects - Instance wide
-- usage : execute system.app_admin.before_btc_all ;

cursor cur is
select distinct 'alter system kill session '''||SID||','||SERIAL#||'''' cmd , username , object_type , owner , object_name
from (
select b.username username, c.object_type object_type , c.owner owner , c.object_name object_name , b.sid sid, b.serial#
from v$locked_object a , v$session b , dba_objects c
where a.object_id = c.object_id and b.sid = a.session_id and a.locked_mode = '3') ;

begin
 dbms_output.put_line('Kill ALL potential blocking sessions');
 nb:=0 ;
 for c_tab in cur loop
  begin
    dbms_output.put_line('Kill session User: '||c_tab.username||' , Object: '||c_tab.object_type||' '||c_tab.owner||'.'||c_tab.object_name||'');

 sql_stmt := c_tab.cmd;
 cn := dbms_sql.open_cursor;
 dbms_sql.parse(cn, sql_stmt,dbms_sql.native);
 ret:=dbms_sql.execute(cn);
 dbms_sql.close_cursor(cn);
 nb:=nb+1;
 exception
  when others then
   dbms_output.put_line(sqlerrm);
  end;
 end loop;

dbms_output.put_line(nb||' killed sessions');
end before_btc_all;

end app_admin;
/


2.3 Calling example

  • STOP APPLICATION using Service


SQL> set serveroutput on
SQL> exec app_admin.stop_app('APPLI');
Kill sessions using dbms_Service for application : APPLI

PL/SQL procedure successfully completed.


Users side :


SQL> select count(1) from user_tables ;
select count(1) from user_tables
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel


  • STOP APPLICATION using Roles


SQL> set serveroutput on

SQL> exec system.app_admin.stop_app('<SCHEMA>');
Kill sessions using Role for application : <SCHEMA>
Kill session username : SMITH
Kill session username : MARTIN
33 killed sessions

PL/SQL procedure successfully completed.


Users side :


killed
SQL> select count(1) from user_tables ;
select count(1) from user_tables
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel


  • START APPLICATION


SQL> set serveroutput on
SQL> exec app_admin.start_app('APPLI');

PL/SQL procedure successfully completed.


Service is now available :


$ lsnrctl stat | grep -i hfb
Service "APPLI" has 1 instance(s).


  • BEFORE_BTC


-- connect auc
SQL> update auc set a = 1000 ; --> lock mode 3

-- then kill blocking session
SQL> set serveroutput on
SQL> exec system.app_admin.before_btc('SCH');
Kill potential blocking sessions for application : SCH
Kill session username : SCH , Object: TABLE SCH
1 killed sessions
PL/SQL procedure successfully completed.

-- user’s session
SQL> commit ;
commit
*
ERROR at line 1:
ORA-00028: your session has been killed


  • BEFORE_BTC_ALL


SQL> set serveroutput on
SQL> execute system.app_admin.before_btc_all ;
Kill ALL potential blocking sessions
Kill session User: SCH0143UT , Object: TABLE SCH0143UT.ENVI
Kill session User: SCH , Object: TABLE SCH.SCH
2 killed sessions

PL/SQL procedure successfully completed.



3] Other stuff

  • find unused existing services

thanks to USERS_MON connection audit table , Real-Time V$SESSION , and Past Sessions from ASH


select distinct name from dba_services A where
not exists ( select '1' from adb.users_mon_31122014 B where A.name = replace(B.service_name,'.domain.name','') )
and not exists ( select '1' from adb.users_mon B where A.name = replace(B.service_name,'.domain.name','') )
and not exists ( select '1' from v$session B where A.name = schemaname )
and not exists ( select '1' from dba_hist_active_sess_history a , dba_users b where A.name = b.username and a.user_id = b.user_id )
order by 1


4] Connect Strings

4.1 JDBC Thin Driver

jdbc:oracle:thin:@<host>:<tcp_port>/<service_name>

jdbc:oracle:thin:@DNS_ALIAS:1521/SRV.domain.fr

more complex syntax, close to TNSNAMES

jdbc:oracle:thin:@(description=(FAILOVER=ON)(address=(protocol=TCP)(Host=bipopeo01.mc2.renault.fr)(port=1556))(address=(protocol=TCP)(Host=bipopeo02.mc2.renault.fr)(port=1556))(connect_data=(service_name=bipopeo01)))

Conf TOMCAT with LDAP access

jdbc:oracle:thin:@ldap://oidsrv-1.corp.fr:389/APPLI_PRO,cn=OracleContext,dc=corp,dc=fr

4.2 SQLPLUS command line

Easy Connect Naming

sqlplus user/psw@<host>:<tcp_port>/<service_name>

sqlplus APPLI/GAM@SRV_NAME:1523/ORA_SRV.corp.fr

sqlplus usr/psw@"cn=APPLI_PRO,cn=OracleContext,dc=corp,dc=fr"

4.3 Reverse on LDAP

ldapsearch -h oidsrv-1.corp.fr -p 389 -D cn=orcladmin -w <psword> -b "cn=OracleContext,dc=corp,dc=fr" -s one "objectclass=orclNetService" orclnetdescstring

ldapsearch -h oidsrv-1.corp.fr -p 389 -D cn=orcladmin -w <psword> -b "cn=OracleContext,dc=corp,dc=fr" -s one "objectclass=alias" orclnetdescstring | grep -ic btc