Tuesday, December 29, 2015

Fixing execution plans using SQL plan baselines

This post shows how to use SQL plan baselines to fix execution plan of a query. This can be used as a replacement for stored outlines.

For this demonstration we create 2 tables each with a row and collect statistics on them.


create  table tab1 (a number, b varchar2(10));
insert into tab1 values(10, 'AA');
create index tab1_i1 on tab1(a);

create  table tab2 (a number, c varchar2(10));
create index tab2_i1 on tab2(a);
insert into tab2 values(10, 'BBB');

exec dbms_stats.gather_table_stats (ownname=>'MURTY', tabname=>'TAB1', estimate_percent=>10, METHOD_OPT=>'FOR ALL COLUMNS SIZE AUTO', cascade=>true);
exec dbms_stats.gather_table_stats (ownname=>'MURTY', tabname=>'TAB2', estimate_percent=>10, METHOD_OPT=>'FOR ALL COLUMNS SIZE AUTO', cascade=>true);

Here is the query for which we will change execution plan using SQL plan baselines.


select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

The execution plan shows nested loop join


SQL> explain plan for select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3684952675

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    13 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |         |     1 |    13 |     4   (0)| 00:00:01 |
|   2 |   NESTED LOOPS               |         |     1 |    13 |     4   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL         | TAB1    |     1 |     6 |     3   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | TAB2_I1 |     1 |       |     0   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |     1 |     7 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("TAB1"."A"="TAB2"."A")

17 rows selected.

If the parameter optimizer_capture_sql_plan_baselines is set to true, after multiple executions of the query, it will create a baseline automatically.


SQL> explain plan for select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
Plan hash value: 3684952675

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    13 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |         |     1 |    13 |     4   (0)| 00:00:01 |
|   2 |   NESTED LOOPS               |         |     1 |    13 |     4   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL         | TAB1    |     1 |     6 |     3   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | TAB2_I1 |     1 |       |     0   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |     1 |     7 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("TAB1"."A"="TAB2"."A")

Note
-----
   - SQL plan baseline "SQL_PLAN_2ppbwajdruq05fc48919a" used for this statement

21 rows selected.

If the parameter optimizer_capture_sql_plan_baselines is set to false, we can create a SQL plan baseline manually using SQL_ID of statement.


SQL> select sql_id, sql_text from v$sql where sql_text like 'select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a';

SQL_ID
-------------
SQL_TEXT
----------------------------------------------------------------------------------------------------------------------------------------------------------------
bqn8dqudd4ajf
select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a

set serveroutput on
declare
  i pls_integer;
BEGIN
  i := dbms_spm.load_plans_from_cursor_cache(sql_id => 'bqn8dqudd4ajf');
  dbms_output.put_line('Plans Loaded: ' || i);
END;
/
Plans Loaded: 1

PL/SQL procedure successfully completed.

Details of SQL Plan baselines can be viewed from dictionary view dba_sql_plan_baselines


SQL> select SQL_HANDLE, SQL_TEXT, PLAN_NAME from dba_sql_plan_baselines where PLAN_NAME='SQL_PLAN_2ppbwajdruq05fc48919a';

SQL_HANDLE                     SQL_TEXT                                                                         PLAN_NAME
------------------------------ -------------------------------------------------------------------------------- ------------------------------
SQL_2ad57c545b7d5805           select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a                SQL_PLAN_2ppbwajdruq05fc48919a

And more details can be get like below using sql_handle


SQL>  select * from table(dbms_xplan.display_sql_plan_baseline( sql_handle=>'SQL_2ad57c545b7d5805', format=>'basic'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------
SQL handle: SQL_2ad57c545b7d5805
SQL text: select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SQL_PLAN_2ppbwajdruq05fc48919a         Plan id: 4232614298
Enabled: YES     Fixed: NO      Accepted: YES     Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------

Plan hash value: 3684952675

------------------------------------------------
| Id  | Operation                    | Name    |
------------------------------------------------
|   0 | SELECT STATEMENT             |         |
|   1 |  NESTED LOOPS                |         |
|   2 |   NESTED LOOPS               |         |
|   3 |    TABLE ACCESS FULL         | TAB1    |
|   4 |    INDEX RANGE SCAN          | TAB2_I1 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |
------------------------------------------------

23 rows selected.

Now we will try to force sort merge join for the same SQL using hint USE_MERGE


SQL> explain plan for select /*+ USE_MERGE(tab1 tab2) */ tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1351227119

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    13 |     6  (17)| 00:00:01 |
|   1 |  MERGE JOIN                  |         |     1 |    13 |     6  (17)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| TAB1    |     1 |     6 |     2   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN           | TAB1_I1 |     1 |       |     1   (0)| 00:00:01 |
|*  4 |   SORT JOIN                  |         |     1 |     7 |     4  (25)| 00:00:01 |
|   5 |    TABLE ACCESS FULL         | TAB2    |     1 |     7 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("TAB1"."A"="TAB2"."A")
       filter("TAB1"."A"="TAB2"."A")

18 rows selected.

Execute once to get details in v$sql_plan, and get SQL_ID


SQL> select /*+ USE_MERGE(tab1 tab2) */ tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

         A B          C
---------- ---------- ----------
        10 AA         BBB
        
SQL> select distinct SQL_ID from v$sql_plan where PLAN_HASH_VALUE=1351227119;

SQL_ID
-------------
fqjpqc2t0byr0

Now we will add the plan that uses sort merge join to SQL plan baseline. So, without using any hint, the execution of query uses sort merge join. Here we are using sql_handle of baseline created for original query, but sql_id and plan_hash_value of hinted query.


set serveroutput on
declare
  i pls_integer;
BEGIN
  i := dbms_spm.load_plans_from_cursor_cache(sql_id => 'fqjpqc2t0byr0', plan_hash_value => 1351227119, sql_handle => 'SQL_2ad57c545b7d5805');
  dbms_output.put_line('Plans Loaded: ' || i);
END;
/
Plans Loaded: 1

PL/SQL procedure successfully completed.

Baseline of original query now shows 2 plans.


SQL> select * from table(dbms_xplan.display_sql_plan_baseline( sql_handle=>'SQL_2ad57c545b7d5805', format=>'basic'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------
SQL handle: SQL_2ad57c545b7d5805
SQL text: select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SQL_PLAN_2ppbwajdruq05e3fe5496         Plan id: 3825095830
Enabled: YES     Fixed: NO      Accepted: YES     Origin: MANUAL-LOAD
--------------------------------------------------------------------------------

Plan hash value: 1351227119

------------------------------------------------
| Id  | Operation                    | Name    |
------------------------------------------------
|   0 | SELECT STATEMENT             |         |
|   1 |  MERGE JOIN                  |         |
|   2 |   TABLE ACCESS BY INDEX ROWID| TAB1    |
|   3 |    INDEX FULL SCAN           | TAB1_I1 |
|   4 |   SORT JOIN                  |         |
|   5 |    TABLE ACCESS FULL         | TAB2    |
------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SQL_PLAN_2ppbwajdruq05fc48919a         Plan id: 4232614298
Enabled: YES     Fixed: NO      Accepted: YES     Origin: AUTO-CAPTURE 
--------------------------------------------------------------------------------

Plan hash value: 3684952675

------------------------------------------------
| Id  | Operation                    | Name    |
------------------------------------------------
|   0 | SELECT STATEMENT             |         |
|   1 |  NESTED LOOPS                |         |
|   2 |   NESTED LOOPS               |         |
|   3 |    TABLE ACCESS FULL         | TAB1    |
|   4 |    INDEX RANGE SCAN          | TAB2_I1 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |
------------------------------------------------

41 rows selected.

But still we see nested loop join plan only for the query.


SQL> explain plan for select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3684952675

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    13 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |         |     1 |    13 |     4   (0)| 00:00:01 |
|   2 |   NESTED LOOPS               |         |     1 |    13 |     4   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL         | TAB1    |     1 |     6 |     3   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | TAB2_I1 |     1 |       |     0   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |     1 |     7 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("TAB1"."A"="TAB2"."A")

Note
-----
   - SQL plan baseline "SQL_PLAN_2ppbwajdruq05fc48919a" used for this statement

21 rows selected.

We will make sort-merge join plan as fixed, so it always uses that fixed execution plan.


declare
  i  pls_integer;
begin
  i := dbms_spm.alter_sql_plan_baseline(sql_handle=>'SQL_2ad57c545b7d5805', plan_name=>'SQL_PLAN_2ppbwajdruq05e3fe5496', attribute_name=>'FIXED', attribute_value=>'YES');
  dbms_output.put_line('Plans altered: ' || i);
end;
/
Plans altered: 1

PL/SQL procedure successfully completed.

Details of baseline now show that sort-merge plan as fixed.


SQL> select * from table(dbms_xplan.display_sql_plan_baseline( sql_handle=>'SQL_2ad57c545b7d5805', format=>'basic'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------
SQL handle: SQL_2ad57c545b7d5805
SQL text: select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SQL_PLAN_2ppbwajdruq05e3fe5496         Plan id: 3825095830
Enabled: YES     Fixed: YES     Accepted: YES     Origin: MANUAL-LOAD
--------------------------------------------------------------------------------

Plan hash value: 1351227119

------------------------------------------------
| Id  | Operation                    | Name    |
------------------------------------------------
|   0 | SELECT STATEMENT             |         |
|   1 |  MERGE JOIN                  |         |
|   2 |   TABLE ACCESS BY INDEX ROWID| TAB1    |
|   3 |    INDEX FULL SCAN           | TAB1_I1 |
|   4 |   SORT JOIN                  |         |
|   5 |    TABLE ACCESS FULL         | TAB2    |
------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SQL_PLAN_2ppbwajdruq05fc48919a         Plan id: 4232614298
Enabled: YES     Fixed: NO      Accepted: YES     Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------

Plan hash value: 3684952675

------------------------------------------------
| Id  | Operation                    | Name    |
------------------------------------------------
|   0 | SELECT STATEMENT             |         |
|   1 |  NESTED LOOPS                |         |
|   2 |   NESTED LOOPS               |         |
|   3 |    TABLE ACCESS FULL         | TAB1    |
|   4 |    INDEX RANGE SCAN          | TAB2_I1 |
|   5 |   TABLE ACCESS BY INDEX ROWID| TAB2    |
------------------------------------------------

41 rows selected.

So, we can get execution plan with sort-merge without any hints.


SQL> explain plan for select tab1.a, tab1.b, tab2.c from tab1, tab2 where tab1.a=tab2.a;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------
Plan hash value: 1351227119

----------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |    13 |     6  (17)| 00:00:01 |
|   1 |  MERGE JOIN                  |         |     1 |    13 |     6  (17)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| TAB1    |     1 |     6 |     2   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN           | TAB1_I1 |     1 |       |     1   (0)| 00:00:01 |
|*  4 |   SORT JOIN                  |         |     1 |     7 |     4  (25)| 00:00:01 |
|   5 |    TABLE ACCESS FULL         | TAB2    |     1 |     7 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("TAB1"."A"="TAB2"."A")
       filter("TAB1"."A"="TAB2"."A")

Note
-----
   - SQL plan baseline "SQL_PLAN_2ppbwajdruq05e3fe5496" used for this statement

22 rows selected.

If we do not like to use plans from SQL Plan baseline, we can set optimizer_use_sql_plan_baselines parameter as false. Also, if we want to drop baseline, we can do it like below.


declare
  i  pls_integer;
begin
  i :=  dbms_spm.drop_sql_plan_baseline(sql_handle=>'SQL_2ad57c545b7d5805',plan_name=>NULL);
  dbms_output.put_line('Plans removed: ' || i);
end;
/
Plans removed: 2

PL/SQL procedure successfully completed.

Thursday, November 26, 2015

Working with snapshot clones of containers stored on Btrfs filesystem

1. To clone a container, we use lxc-clone command. The option -B btrfs creates a Btrfs volume which later can be used for snapshot clones. Snapshot clones are similar to linked clones of Virtualbox.

# lxc-clone -B btrfs c1 c2

2. We can use -s option to create a snapshot volume.

# lxc-clone -s -B btrfs  c2 c3

Here are some useful Btrfs commands to get more information about underlying volumes of Btrfs.

1. Listing subvolumes

# btrfs subvolume list /vm
ID 257 gen 187 top level 5 path vm
ID 260 gen 168 top level 257 path c2/rootfs
ID 261 gen 168 top level 257 path c3/rootfs

2. Getting details of a subvolume

# btrfs subvolume show /vm/c2/rootfs
/vm/c2/rootfs
    Name:                  rootfs
    uuid:                  68628679-d0ae-724a-be01-5ba74e2c00b1
    Parent uuid:           -
    Creation time:         2015-11-24 17:34:33
    Object ID:             260
    Generation (Gen):      168
    Gen at creation:       161
    Parent:                257
    Top Level:             257
    Flags:                 -
    Snapshot(s):
                           vm/c3/rootfs

3. Enable quotas and see disk usage of subvolumes

# btrfs quota enable /vm

# btrfs qgroup show  /vm
qgroupid rfer      excl      
-------- ----      ----      
0/5      16384     16384     
0/257    827707392 827707392 
0/260    825425920 7421952   
0/261    825425920 7421952   
0/263    825425920 7421952

Here rfer column shows total bytes of data referred by subvolume, and excl column shows bytes of data exclusive for the volume that is not shared with other volumes.

Working with LXC containers

1. We use lxc-create command to create a new container. But it installs OS in container from the CentOS repositories available on Internet. But, to use a local repository, we can set repo environment variable for this.

# export repo="http://127.0.0.1/centos7_1503"

2. Create a container with below command, here c1 is container name.

# lxc-create -t centos -n c1

3. Commands to stop, start, and attach to console of container are

# lxc-stop -n c1
# lxc-start -n c1
# lxc-attach -n c1

4. When we stop container using lxc-stop command, it send SIGPWR signal to container. But, CentsOS 7 does not handle SIGPWR correctly to poweroff. So, here is the fix we have execute in container OS.

[root@c1 ~]# cd /usr/lib/systemd/system
[root@c1 ~]# ln -s poweroff.target sigpwr.target

5. LXC container creation script for CentOS does Minimal Install. To have a good working set of packages, configure yum and install Base group of packages.

Add following lines to file /etc/yum.repos.d/local.repo

[local]
name=local_centos7_1503
baseurl=http://192.168.2.1/centos7_1503
gpgcheck=0

Install Base group

[root@c1 ~]# yum groupinstall Base

Installing LXC from source code on a CentOS 7 host, and configuring

1. Before beginning to install LXC, the pre-requisite package libcap-devel should be installed. This assumes Yum configured on host already.

# yum install libcap-devel

2. Unzip LXC source code.

# tar -zxvf lxc-1.1.5.tar.gz
# cd lxc-1.1.5

3. Install with following commands. By default, LXC will be installed in /usr/local directory. So, we have to mention correct directory names by specifying options for ./configure command.

# ./autogen.sh
# ./configure --enable-capabilities --prefix=/usr --sysconfdir=/etc --localstatedir=/var
# make
# make install

4. Add following configuration information in /etc/sysconfig/lxc-net (new file) to configure networking for LXC host.

LXC_BRIDGE="lxcbr0"
USE_LXC_BRIDGE="true"

LXC_ADDR="192.168.2.1"
LXC_NETMASK="255.255.255.0"
LXC_DHCP_RANGE="192.168.2.70,192.168.2.99"

5. Add following line in /root/.bash_profile and /etc/init.d/lxc-net files to make LXC libraries available for startup script and environment.

# export LD_LIBRARY_PATH=/usr/lib

6. Start the service lxc-net and make it autostart during system boot.

# service lxc-net start
# chkconfig lxc-net on

7. Verify if lxcbr0 interface is showing up in ifconfig output.

# ifconfig
....
lxcbr0: flags=4163  mtu 1500
        inet 192.168.2.1  netmask 255.255.255.0  broadcast 0.0.0.0
....

8. Add following line in /etc/lxc/lxc.conf (new file) to specify where all containers' root file systems should be stored.

lxc.lxcpath = /vm

Thursday, April 23, 2015

Filtering inner (null-supplying) table in outer join

Let us consider below tables to demonstrate filtering inner table in outer join

SQL> select * from t1;

         A          B
---------- ----------
         1         10
         2         20
         3         30

3 rows selected.

SQL> select * from t2;

         B          C
---------- ----------
        20        200
        30        300
        40        400

3 rows selected.

This is regular outer join where t1 is outer (row preserving) table and t2 is inner (null supplying) table.

SQL> select a, t1.b t1_b, t2.b t2_b, c from t1 left outer join t2 on t1.b = t2.b;

         A       T1_B       T2_B          C
---------- ---------- ---------- ----------
         2         20         20        200
         3         30         30        300
         1         10

3 rows selected.

Filter predicate in WHERE clause: It filters from result like below, after performing join operation.

SQL> select a, t1.b t1_b, t2.b t2_b, c from t1 left outer join t2 on t1.b = t2.b where t2.c=300;

         A       T1_B       T2_B          C
---------- ---------- ---------- ----------
         3         30         30        300

1 row selected.

Filter predicate in join condition: It filters rows from inner (null supplying) table t2, before performing join operation.

SQL> select a, t1.b t1_b, t2.b t2_b, c from t1 left outer join t2 on t1.b = t2.b and t2.c=300;

         A       T1_B       T2_B          C
---------- ---------- ---------- ----------
         3         30         30        300
         2         20
         1         10

3 rows selected.

Here are the equivalent SQLs in Oracle's dialect.

SQL> select a, t1.b t1_b, t2.b t2_b, c from t1, t2 where t1.b = t2.b(+) and t2.c=300;

         A       T1_B       T2_B          C
---------- ---------- ---------- ----------
         3         30         30        300

1 row selected.

Observe (+) in filter predicate of t2 below, which actually filters rows before join operation.

SQL> select a, t1.b t1_b, t2.b t2_b, c from t1, t2 where t1.b = t2.b(+) and t2.c(+)=300;

         A       T1_B       T2_B          C
---------- ---------- ---------- ----------
         3         30         30        300
         2         20
         1         10

3 rows selected.

Unzip multi part zip archive on Linux

This works with zip command version 3.0 or above (available in RHEL 6 onwards). Here is an example that unzips multi part zip archive of an Informatica software.


# ls -l dac_win_11g_infa_linux_64bit_951*
-rw-r--r-- 1 root root 2097152000 Nov 12  2013 dac_win_11g_infa_linux_64bit_951.z01
-rw-r--r-- 1 root root 2097152000 Nov 12  2013 dac_win_11g_infa_linux_64bit_951.z02
-rw-r--r-- 1 root root 2097152000 Nov 12  2013 dac_win_11g_infa_linux_64bit_951.z03
-rw-r--r-- 1 root root 1060733887 Nov 12  2013 dac_win_11g_infa_linux_64bit_951.zip

Note: there is a hyphen before and after 's' in below command
# zip -s- dac_win_11g_infa_linux_64bit_951.zip --out inf2.zip
 copying: 951HF2_Client_Installer_win32-x86.zip
 copying: 951HF2_Server_Installer_linux-x64.tar
 copying: DAC11gInstaller.zip
 copying: Infa951Docs.zip
 copying: Oracle_All_OS_Prod.key

# unzip inf2.zip
Archive:  inf2.zip
 extracting: 951HF2_Client_Installer_win32-x86.zip  
  inflating: 951HF2_Server_Installer_linux-x64.tar  
 extracting: DAC11gInstaller.zip     
 extracting: Infa951Docs.zip         
  inflating: Oracle_All_OS_Prod.key