1. DML锁 数据操纵语言(Data Manipulation Language),SELECT、INSERT、UPDATE、MERGE和DELETE语句。
DML锁机制允许并发执行数据修改。例如,DML锁可能是特定数据行上的锁,或者是锁定表中所有行的表级锁。
2. DDL锁 数据定义语言(Data Definition Language),如CREATE和ALTER语句等。
DDL锁可以保护对象结构定义。
3. 内部锁和闩 保护Oracle内部数据结构。例如,Oracle解析一个查询并生成优化的查询计划时,它会把库缓存“临时闩”,将计划放在那里,以供其他会话使用。
闩(latch)是Oracle采用的一种轻量级的低级串行化设备,功能上类似于锁。闩是数据库中导致竞争的一个常见原因。
轻量级指的是闩的实现,而不是闩的作用。
事务发起第一个修改时会得到TX锁,而且会一直持有这个锁,直至事务执行提交(COMMIT)或回滚(ROLLBACK)。
TX锁用作一种排队机制,使得其他会话可以等待这个事务执行。事务中修改或通过SELECT FOR UPDATE选择的每一行都会“指向”该事务的一个相关TX锁。
在Oracle中,闩为数据的一个属性。Oracle并没有一个传统的锁管理器,不会为系统中锁定的每一行维护一个长长的列表。
如果数据库中有一个传统的基于内存的锁管理器,在这样一个数据库中,对一行锁定的过程一般如下:在Oracle中对数据行锁定时,行指向事务ID的一个副本,事务ID存储在包含数据的块中,释放锁时,事务ID却会保留下来。
这个事务ID是事务所独有的,表示了回滚段号、槽和序列号。
事务ID留在包含数据行的块上,可以告诉其他会话:“拥有”这个数据(并非块上的所有数据,只是修改的那一行)。
另一个会话到来时,它会看到锁ID,由于锁ID表示一个事务,所以可以很快地查看持有这个锁的事务是否还是活动的。
如果锁不活动,则允许会话访问这个数据。如果锁还是活动的,会话就会要求一旦释放锁就得到通知。
因此,这就有了一个排队机制: 请求锁的会话会排队,等待目前拥有锁的事务执行,然后得到数据。scott@ORCL>create table dept_bak as select * from dept;
表已创建。
scott@ORCL>update dept_bak set deptno = deptno+10;
已更新4行。
scott@ORCL>select username,
2 v$lock.sid,
3 trunc(id1/power(2,16)) rbs,
4 bitand(id1,to_number('ffff','xxxx'))+0 slot,
5 id2 seq,
6 lmode,
7 request
8 from v$lock, v$session
9 where v$lock.type = 'TX'
10 and v$lock.sid = v$session.sid
11 and v$session.username = USER;
USERNAME SID RBS SLOT SEQ LMODE REQUEST
------------------------------ ---------- ---------- ---------- ---------- ---------- ----------
SCOTT 68 6 0 2146 6 0
scott@ORCL>select XIDUSN, XIDSLOT, XIDSQN
2 from v$transaction;
XIDUSN XIDSLOT XIDSQN
---------- ---------- ----------
6 0 2146
1. $LOCK表中的LMODE为6,REQUEST为0。
LMODE=6是一个排他锁。
请求(REQUEST)值为0则意味着没有发出请求;也就是说,你拥有这个锁。
scott@ORCL>update emp_bak set ename = lower(ename);
已更新15行。
scott@ORCL>update dept_bak set deptno = deptno-10;
scott@ORCL>select username,
2 v$lock.sid,
3 trunc(id1/power(2,16)) rbs,
4 bitand(id1,to_number('ffff','xxxx'))+0 slot,
5 id2 seq,
6 lmode,
7 request
8 from v$lock, v$session
9 where v$lock.type = 'TX'
10 and v$lock.sid = v$session.sid
11 and v$session.username = USER;
USERNAME SID RBS SLOT SEQ LMODE REQUEST
------------------------------ ---------- ---------- ---------- ---------- ---------- ----------
SCOTT 133 6 0 2146 0 6
SCOTT 68 6 0 2146 6 0
SCOTT 133 8 20 2330 6 0
scott@ORCL>select XIDUSN, XIDSLOT, XIDSQN
2 from v$transaction;
XIDUSN XIDSLOT XIDSQN
---------- ---------- ----------
6 0 2146
8 20 2330
可以看到一个新的事务(8,20,2330)
scott@ORCL>select
2 (select username from v$session where sid=a.sid) blocker,
3 a.sid,
4 ' is blocking ',
5 (select username from v$session where sid=b.sid) blockee,
6 b.sid
7 from v$lock a, v$lock b
8 where a.block = 1
9 and b.request > 0
10 and a.id1 = b.id1
11 and a.id2 = b.id2;
BLOCKER SID 'ISBLOCKING' BLOCKEE SID
------------------------------ ---------- ------------- ---------- ------
SCOTT 68 is blocking SCOTT 133
现在,如果提交原来的事务(SID=68),并重新运行锁查询,可以看到请求行不见了:
scott@ORCL>commit;
提交完成。
scott@ORCL>select username,
2 v$lock.sid,
3 trunc(id1/power(2,16)) rbs,
4 bitand(id1,to_number('ffff','xxxx'))+0 slot,
5 id2 seq,
6 lmode,
7 request
8 from v$lock, v$session
9 where v$lock.type = 'TX'
10 and v$lock.sid = v$session.sid
11 and v$session.username = USER;
USERNAME SID RBS SLOT SEQ LMODE REQUEST
------------------------------ ---------- ---------- ---------- ---------- ----- ----------
SCOTT 133 8 20 2330 6 0
scott@ORCL>select XIDUSN, XIDSLOT, XIDSQN
2 from v$transaction;
XIDUSN XIDSLOT XIDSQN
---------- ---------- ----------
8 20 2330
另一个会话一旦放弃锁,请求行就会消失。这个请求行就是排队机制。一旦事务执行,数据库会唤醒被阻塞的会话。
scott@ORCL>create table t ( x int ) maxtrans 2;
表已创建。
scott@ORCL>insert into t select rownum from all_users;
已创建38行。
scott@ORCL>commit;
提交完成。
scott@ORCL>select distinct dbms_rowid.rowid_block_number(rowid) from t;
DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------
3612
因此,我们有24行,而且经验证,它们都在同一个数据库块上。现在,在一个会话中发出以下命令:
scott@ORCL>update t set x = 1 where x = 1;
已更新 1 行。
scott@ORCL>update t set x = 2 where x = 2;
已更新 1 行。
scott@ORCL>update t set x = 3 where x = 3;
现在,由于这3行在同一个数据库块上,而且我们将MAXTRANS(该块的最大并发度)设置为2,所以第3个会话会被阻塞。
scott@ORCL>drop table t;
drop table t
*
第 1 行出现错误:
ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源, 或者超时失效
如果你要执行的操作将要阻塞,但是这个操作不允许阻塞,总是会得到这样一条一般性的消息。
scott@ORCL>update t set x = 1 where x = 1;
已更新 1 行。
scott@ORCL>update t set x = 1 where x = 1;
已更新 1 行。
scott@ORCL>commit;
提交完成。
scott@ORCL>update t set x = 1 where x = 1;
已更新 1 行。
scott@ORCL>create table t1 ( x int );
表已创建。
scott@ORCL>create table t2 ( x int );
表已创建。
scott@ORCL>insert into t1 values ( 1 );
已创建 1 行。
scott@ORCL>insert into t2 values ( 1 );
已创建 1 行。
scott@ORCL>select (select username
2 from v$session
3 where sid = v$lock.sid) username,
4 sid,
5 id1,
6 id2,
7 lmode,
8 request, block, v$lock.type
9 from v$lock
10 where sid = (select sid
11 from v$mystat
12 where rownum=1)
13 /
USERNAME SID ID1 ID2 LMODE REQUEST BLOCK TY
------------------------------ ---------- ---------- ---------- ---------- ---------- ---------- --
SCOTT 68 100 0 4 0 0 AE
SCOTT 68 65921 1 3 0 0 TO
SCOTT 68 77102 0 3 0 0 TM
SCOTT 68 77103 0 3 0 0 TM
SCOTT 68 131090 2131 6 0 0 TX
scott@ORCL>select object_name, object_id
2 from user_objects
3 where object_name in ('T1','T2')
4 /
OBJECT_NAME OBJECT_ID
------------ ----------
T1 77102
T2 77103
每个事务只能得到一个TX锁,但是TM锁则不同,修改了多少个对象,就能得到多少个TM锁。
TM锁的ID1列就是DML锁定对象的对象ID,所以 很容易发现哪个对象持有这个锁。
系统中允许的TM锁总数可以配置。这个数可能设置为0。但这并不是说 数据库变成了一个只读数据库(没有锁),而是说不允许DDL。在非常专业的应用(如RAC实现)中,这一点 可以减少实例内可能发生的协调次数。Alter table t add new_column date;
在执行这条语句时,表T不能被别人修改。在此期间,可以使用SELECT查询这个表,但是大多数其他操作都不允许执行,包括所有DDL语句。
create index t_idx on t(x) ONLINE;
ONLINE关键字会改变具体建立索引的方法。Oracle并不是加一个排他DDL锁来防止数据修改,而只会试图得到表上的一个低级(mode 2)TM锁。
防止其他DDL发生,同时还允许DML正常进行。
为DDL语句执行期间对表所做的修改维护一个记录,执行CREATE时再把这些修改应用至新的索引。
这样能大大增加数据的可用性。
Create view MyView
as
select emp.*
from emp, dept
where emp.deptno = dept.deptno;
表EMP和DEPT上都会加共享DDL锁,而CREATE VIEW命令仍在处理。可以修改这些表的内容,但是不能修改它们的结构。
scott@ORCL>select session_id sid, owner, name, type,
2 mode_held held, mode_requested request
3 from dba_ddl_locks;
SID OWNER NAME TYPE HELD REQUEST
---------- ------ --------------------------- ------------- ------ ---------
190 SYS "SYS"."ALERT_QUE":"HAE_SUB" 28 Null None
190 SYS STANDARD Table/Procedure/Type Null None
190 SYS DBMS_PRVT_TRACE Table/Procedure/Type Null None
131 SYS DBMS_OUTPUT Body Null None
131 SYS DBMS_OUTPUT Table/Procedure/Type Null None
190 SYS AQ$_ALERT_QT_E 10 Null None
131 SYS DBMS_APPLICATION_INFO Body Null None
190 SYS DBMS_HA_ALERTS_PRVT Table/Procedure/Type Null None
190 SYS STANDARD Body Null None
8 SYS DBMS_LOGSTDBY Body Null None
190 SYS ALERT_QUE_R 23 Null None
190 SYS ALERT_QUE_R 23 Null None
131 SYS PLITBLM Table/Procedure/Type Null None
190 SYS PLITBLM Table/Procedure/Type Null None
190 SYS ALERT_QUE 10 Null None
131 SYS DBMS_APPLICATION_INFO Table/Procedure/Type Null None
190 SYS DBMS_HA_ALERTS_PRVT Body Null None
131 SCOTT SCOTT 18 Null None
131 SCOTT 73 Share None
131 SYS DATABASE 18 Null None
8 SYS DBMS_LOGSTDBY Table/Procedure/Type Null None
190 SYS "SYS"."ALERT_QUE":"LOCALHOST_3 28 Null None
192 SYS "SYS"."AQ$_ALERT_QT_E" 28 Null None
190 SYS DBMS_PRVT_TRACE Body Null None
已选择24行。
这些就是我的会话“锁定”的所有对象。我对一组DBMS_*包加了可中断解析锁。这是使用SQL*Plus的副作用;例如,它会调用DBMS_APPLICATION_INFO。
scott@ORCL>create or replace procedure pp as begin null; end;
2 /
过程已创建。
scott@ORCL>exec pp
PL/SQL 过程已成功完成。
过程PP现在会出现在DBA_DDL_LOCKS视图中。我们有这个过程的一个解析锁:
scott@ORCL>select session_id sid, owner, name, type,
2 mode_held held, mode_requested request
3 from dba_ddl_locks
4 /
SID OWNER NAME TYPE HELD REQUEST
---------- ------ -------------------- ---------------------- ----- -----
131 SYS DBMS_OUTPUT Body Null None
190 SYS ALERT_QUE 10 Null None
131 SYS DBMS_APPLICATION_INFO Table/Procedure/Type Null None
190 SYS DBMS_HA_ALERTS_PRVT Body Null None
131 SCOTT PP Table/Procedure/Type Null None
......
已选择26行。
可以看到,现在这个视图中没有P了。我们的解析锁被中断了。
import java.sql.*;
public class instest
{
static public void main(String args[]) throws Exception{
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
Connection conn = DriverManager.getConnection
("jdbc:oracle:thin:@dellpe:1521:ora10gr1",
"scott","tiger");
conn.setAutoCommit( false );
Statement stmt = conn.createStatement();
for( int i = 0; i < 25000; i++ ){
stmt.execute("insert into "+ args[0] + " (x) values(" + i + ")" );
}
conn.commit();
conn.close();
}
}
import java.sql.*;
public class instest
{
static public void main(String args[]) throws Exception{
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
Connection conn = DriverManager.getConnection
("jdbc:oracle:thin:@dellpe:1521:ora10gr1",
"scott","tiger");
conn.setAutoCommit( false );
PreparedStatement pstmt = conn.prepareStatement("insert into "+ args[0] + " (x) values(?)" );
for( int i = 0; i < 25000; i++ ){
pstmt.setInt( 1, i );
pstmt.executeUpdate();
}
conn.commit();
conn.close();
}
}
没有使用绑定变量时,我们的CPU时间中有5/6的时间都用于解析SQL。