锁机制用于管理对共享资源的并发访问,并支持数据的完整性和一致性。
一、 锁的分类DML、DDL锁。
1. DML锁:用于确保对共享数据的当前独享。读不会产生锁(除了select for update),它并不阻塞其他DML操作。
TX锁,事务锁,当事务发起第一个修改时会得到该锁,并一直持有,知道事务结束(提交或回滚),是一个队列机制。对于tm锁,id1就是object_id,id2为0,对于tx锁id1是以十进制数值表示事务占用的回滚段号和事务solt number,具体算法就是把id1除 以2的16次方,余数就是solt number,结果的取整数值是回滚段号
TM锁,用于确保修改表的内容时,表的结构不会发生改变。
2. DDL锁:在DDL操作过程中会自动为对象加DDL锁,从而保护这些对象不被更改。
排他DDL锁:不允许修改表的结构,也不允许对表进行数据的更改,只可以查询该表数据。
共享DDL锁:不允许修改表的机构,可以修改和读数据。
可中断解析锁:维持对象之间的依赖性,如果在被依赖对象上执行DDL,那么依赖与该对象的其他对象会置为无效。
DDL锁会产生隐式提交,无论该DDL是否成功。如果不想产生隐式提交,可以使用自治事务:在匿名块中声明了一个自治事务的存储过程,用于动态执行ddl。这样ddl语句不会对dml语句产生隐式提交。
declare
procedure no_commit_ddl(v_sql in varchar2) is
pragma AUTONOMOUS_TRANSACTION;
begin
execute immediate v_sql;
end;
begin
insert into t values(1);
no_commit_ddl('drop table t1 purge');
end;
二、 oracle对数据锁定的实现方式:
在执行DML操作时,会产生TX锁(事务锁)。ORACLE在SGA中有分配两个内存结构,一个是Resource Structure和Lock Structure通过队列的数据结构和算法实现锁。Resource Structure有三个与并发控制有关的成员:Owner,Waiter,Converter。它们其实是指向Lock Structure链表的指针。分别表示已经获取资料访问权的进程、等待进程、锁转换等待进程。 Lock Structure用于记录访问共享资源的数据结构,它记录了事务相关信息,锁模式、进程ID等信息 。当一个进程访问共享资源时,必须要去锁定该资源,所以它先要从内存中申请一个Lock Structure,然后根据封锁是否成功加入不同的链表。事务会定位到数据所在的数据块上,然后根据块头信息定位到行,查看改行的LB(Lock Byte锁标识位)。然后再检查LB指向的ITL(存存于该行数据所在的块头,包含了事务的信息:ITL序号、事务编号、回滚段地址、事务标识、锁定的行数、SCN/FSC)。跟据是否被锁定且是active的,如果是则把这个Lock Structure挂到Resource Structure的Waiter链表中。如果LB指向ITL对应的事务状态不是active,则将Lock Structure挂到Resource Structure的Owner链表,然后在回滚段中获得一个事务槽、分配空间,创建数据的前镜像,然后到块头申请ITL表项,并修改block的itl Lck标志位,修改记录lb标志位指向事务所在的itl。事务结束时,将会去检查Waiter和Conversion链表,Conversion链表优先,将锁分配给最先进入队列的请求者。
这样的内存结构设计和将锁信息保存在数据块头和行头的方式,减少了内存的开销,同时也就解释了为什么在修改多行记录时,TX锁在v$lock中只有一条记录。TX锁,只是一个内存结构,既Lock Structure,只记录事务和发起事务的线程信息。而判断该事务是否锁定数据,是通过数据块中的信息得到的。
详见http://blog.itpub.net/9683969/viewspace-672920
三、 锁相关视图:
V$lock
这个视图列出了所有数据库中的所有锁(locks)和闩(latches)和所有在一个lock或者latch上的请求。简述如下:
Addr
处于lock状态的对象的地址
Kaddr
lock的地址
Sid
session id
Type
用户或者系统锁的类型,用户锁例如:TM-DML入队,
TX-事务入队,UL-用户提供的(User supplied)
系统类型的锁定诸如:SMON指派恢复进程,临时段入队,
分配新的块入队,redo线程全局入队,
写redo log的入队(media recovery)等等
Id1、Id2
对于tm锁,id1就是object_id,id2为0,对于tx锁id1是以十进制数值表示事务占用的回滚段号和事务solt number,具体算法就是把id1除以2的16次方,余数就是solt number。
LMODE(锁定模式)和REQUEST(请求的锁定模式)
0、无
1、NULL,可以某些情况下,如分布式数据库的查询会产生此锁。
2、SS,表结构共享锁
3、SX,表结构共享锁+被操作的记录的排它锁
4、S,表结构共享锁+所有记录共享锁
5、SRX表结构共享锁+所有记录排它锁
6、X 表结构排它锁+所有记录排它锁
对于锁的模式之间的互斥关系,如下:
Block
当前的锁是否阻塞了其他的锁。
查询脚本:
v$transaction
ADDR Address of the transaction state object事务对象地址
XIDUSN Undo segment number回滚段编号
XIDSLOT Slot number槽(slot)编号
XIDSQN Sequence number序列编号
UBAFIL Undo block address (UBA) filenum回滚块地址在的文件序号
UBABLK UBA block number uba块数量
UBASQN UBA sequence number uba序列编号
UBAREC UBA record number uba记录数
STATUS Status 事务状态
START_TIME Start time (wall clock)
START_SCNB Start system change number (SCN) base
START_SCNW Start SCN wrap
START_UEXT Start extent number
START_UBAFIL Start UBA file number
START_UBABLK Start UBA block number
START_UBASQN Start UBA sequence number
START_UBAREC Start UBA record number
SES_ADDR User session object address用户会话对象地址
FLAG Flag
SPACE YES if a space transaction是否是空间事务
RECURSIVE YES if a recursive transaction是否是递归事务
NOUNDO YES if a no undo transaction是否是非撤销事务
PTX YES if parallel transaction 是否是并行事务
NAME Name of a named transaction是否命名事务
PRV_XIDUSN Previous transaction undo segment number
PRV_XIDSLT Previous transaction slot number
PRV_XIDSQN Previous transaction sequence number
PTX_XIDUSN Rollback segment number of the parent XID
PTX_XIDSLT Slot number of the parent XID
PTX_XIDSQN Sequence number of the parent XID
DSCN-B Dependent SCN base
DSCN-W Dependent SCN wrap
USED_UBLK Number of undo blocks used使用的撤销块数
USED_UREC Number of undo records used书用的撤销记录数
LOG_IO Logical I/O 逻辑io
PHY_IO Physical I/O 物理io
CR_GET Consistent gets一致性读
CR_CHANGE Consistent changes一致性更改
可以用以下语句查询锁:
select /*+ rule*/
a.TYPE,
trunc(a.ID1/power(2,16)) rollback_seq,
mod(a.ID1,power(2,16)) slot,
a.ID2 seq,
a.LMODE,
d.XIDUSN,
d.XIDSLOT,
d.XIDSQN,
e.OBJECT_ID,
e.LOCKED_MODE
from v$lock a, (select sid from v$mystat where rownum = 1) b, dba_lock c,
v$transaction d,v$locked_object e
where a.SID = b.SID
and a.SID = c.session_id
and a.ID1 = c.lock_id1
and a.TYPE = 'TX'
and a.SID = e.SESSION_ID
and a.ADDR = d.ADDR;
四、 锁相关参数:
SQL> show parameter DML_LOCKS;
NAME TYPE VALUE
------------------------------------ -----------
dml_locks integer 748
这个参数用来规定TM锁的总数,要获得表锁需要TM锁。
SQL> show parameter ROW_LOCKING;
NAME TYPE VALUE
------------------------------------ -----------
row_locking string always
这个参数指定行锁的方式,ROW_LOCKING该参数指定行封锁方式。若设置为ALWAYS,则在修改表时只实施行封锁。若设置为INTENT时,则行封锁只适用于SELECT FOR UPDATE,而在修改时实施表封锁。
五、 锁和阻塞
单个事务会产生锁,但是不会产生阻塞,当事务并发时,形成对共享事务的争用会产生阻塞。
1. INSERT产生的阻塞:
情况1:插入的两条记录同一主键值。这通常是由于设计不佳,采用用户自动生成主键导致。
会话一:
SQL> insert into test values(1);
1 row created.
会话二产生阻塞:
SQL> insert into test values(1);
会话一sid以及锁的情况:
SQL> select sid from v$mystat where rownum=1;
SID
----------
762
SQL> select /*+rule*/ sid,type,id1,id2,lmode,request,block from v$lock where type in('TX','TM') order by 1;
SID TY ID1 ID2 LMODE REQUEST BLOCK
---------- -- ---------- ---------- ---------- ---------- ----------
256 TX 1048604 101774 0 4 0
256 TM 1977608 0 3 0 0
256 TX 786445 171353 6 0 0
762 TM 1977608 0 3 0 0
762 TX 1048604 101774 6 0 1
解决方法:
1. 使用序列或sys_guid()生成唯一键值
2. 如果必须由用户生成,并且有可能生成相同键值的情况下,可以串行化事务,将主键值hash成一个锁(排他锁),并向oracle申请独占这个锁。可以放在存储过程中实现,也可以放在事前触发器中实现。用户自定义锁,返回状态如下:
|
0 |
Success |
|
1 |
Timeout |
|
2 |
Deadlock |
|
3 |
Parameter error |
|
4 |
Already own lock specified byidorlockhandle |
|
5 |
Illegal lock handle |
dbms_lock.request(id=>1000,lockmode=>DBMS_LOCK.X_MODE,timeout=>0,release_on_commit=>true)。
一般情况下,merge,update,delete都会产生行锁,会互相阻塞,但大多数情况下不会与INSERT(除更新主键值)产生阻塞,除非向上个例子,update一个已经存在的主键。看一个例子,update和delte产生的阻塞。
回话1中更新记录,不提交
scott@ZK> insert into t values(1);
1 row created.
scott@ZK> commit;
Commit complete.
scott@ZK> update t set id=2 where id=1;
1 row updated.
scott@ZK> select sid from v$mystat where rownum=1;
SID
----------
1
scott@ZK> select sid,type,lmode,request,block from v$lock where type in('TX','TM') order by 1;
SID TY LMODE REQUEST BLOCK
---------- -- ---------- ---------- ----------
1 TM 3 0 0
1 TX 6 0 1
30 TX 0 6 0
30 TM 3 0 0
回话2产生阻塞。
scott@ZK> delete t where id=1;
2. 关于外键子表不加索引产生阻塞问题
内容太多,详见:外键不建索引导致阻塞问题
六、 死锁:
两个回话,都在等待对方释放资源,就会产生死锁。
实际应用中,出现死锁很可能是应用设计的不好,导致死锁产生。实际中碰到的有如下几种:
1. 程序设计不合理,互相等待对方释放资源
T1时刻:
回话1
scott@ZK> delete d_lock2 where id=1;
1 row deleted.
回话2:
scott@ZK> delete d_lock1 where id=1;
1 row deleted.
T2时刻:
回话1,先阻塞后死锁:
scott@ZK> delete d_lock1 where id=1;
delete d_lock1 where id=1
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
回话2一直在阻塞
scott@ZK> delete d_lock2 where id=1;
1 row deleted.
2. 外键未加索引
上面已经介绍过关于外键未建索引导致阻塞的问题,继续利用上面的例子来说明。通过上面的例子可以很明显看出来,当子表dml时,很容易出现对父表的锁。利用这一点构造死锁:
回话1和回话2分别执行删除子表的操作:
scott@ZK> delete c where cid=1;
1 row deleted.
scott@ZK> delete c where cid=2;
1 row deleted.
SID TY OBJECT_NAME ID1 ID2 LMODE REQUEST BLOCK
---------- -- -------------------- ---------- ---------- ---------- ---------- ----------
1 TX 131072 1274 6 0 0
1 TM P 76209 0 3 0 0
1 TM C 76211 0 3 0 0
31 TX 524319 1491 6 0 0
31 TM C 76211 0 3 0 0
31 TM P 76209 0 3 0 0
此时两个回话都在父表和从表中加入了锁,此时对附表进行dml操作会阻塞,那么分别对父表操作,就会构成锁,原因就是都在等待对方释放锁:
回话1先执行,所以先报错了
scott@ZK> delete p where pid=5;
delete p where pid=5
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
回话2,一直在阻塞,直到发生死锁的回话被kill或是结束事务。
scott@ZK> delete p where pid=6;
我认为既然建立了外键,那么最好建立索引,虽然有很多可以外键不建立索引的原因。下面给出Tom关于查询没有建立索引的外键脚本:
col table_name for a30
col constraint_name for a30
col columns for a50
select table_name,constraint_name,
cname1||nvl2(cname2,','||cname2,null)||nvl2(cname3,','||cname3,null)||
nvl2(cname4,','||cname4,null)||nvl2(cname5,','||cname5,null)||
nvl2(cname6,','||cname6,null)||nvl2(cname7,','||cname7,null)||nvl2(cname8,','||cname8,null) columns
from(
select b.table_name,b.constraint_name,
max(decode(position,1,column_name,null)) cname1,
max(decode(position,2,column_name,null)) cname2,
max(decode(position,3,column_name,null)) cname3,
max(decode(position,4,column_name,null)) cname4,
max(decode(position,5,column_name,null)) cname5,
max(decode(position,6,column_name,null)) cname6,
max(decode(position,7,column_name,null)) cname7,
max(decode(position,8,column_name,null)) cname8,
count(1) col_cnt
from
(select table_name,constraint_name,column_name,position from user_cons_columns) a,
user_constraints b
where a.constraint_name = b.constraint_name
and b.constraint_type = 'R'
group by b.table_name,b.constraint_name
) cons
where col_cnt>ALL
(select count(1) from user_ind_columns i
where i.table_name = cons.table_name
and i.column_name in (cname1,cname2,cname3,cname4,cname5,cname6,cname7,cname8)
and i.column_position <= cons.col_cnt
group by i.index_name
);
3. 更新位图索引所在字段
表bit_tbl上name字段上建立了位图索引,只有'Y'和'N'两个值,现在更新索引字段,其实是锁住了这个索引所对应的所有记录
回话1:
scott@ZK> update bit_tbl set name='Y' where id=1;
1 row updated.
回话2:
scott@ZK> delete bit_tbl where id=2;
delete bit_tbl where id=2
回话1再执行如下语句时,回话2报死锁
scott@ZK> delete bit_tbl where id=2;
1 row deleted.
回话2:
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
死锁定位与处理:
死锁定位很简单,通过查看告警日志生成的跟踪文件查看:
ORA-00060: Deadlock detected. More info in file /u01/diag/rdbms/zk/zk/trace/zk_ora_4995.trc.
然后在跟踪文件中进行查看,确定造成死锁的语句(标黑粗字):
Deadlock graph:
---------Blocker(s)-------- ---------Waiter(s)---------
Resource Name process session holds waits process session holds waits
TX-00030003-000004ae 19 1 X 23 29 X
TX-0009001d-000004a7 23 29 X 19 1 X
session 1: DID 0001-0013-0000000F session 29: DID 0001-0017-00000006
session 29: DID 0001-0017-00000006 session 1: DID 0001-0013-0000000F
Rows waited on:
Session 1: obj - rowid = 00012943 - AAASlDAAEAAAAn/AAA
(dictionary objn - 76099, file - 4, block - 2559, slot - 0)
Session 29: obj - rowid = 00012944 - AAASlEAAEAAAAIPAAA
(dictionary objn - 76100, file - 4, block - 527, slot - 0)
----- Information for the OTHER waiting sessions -----
Session 29:
sid: 29 ser: 25 audsid: 1390610 user: 84/SCOTT flags: 0x45
pid: 23 O/S info: user: oracle, term: UNKNOWN, ospid: 5066
image: oracle@localhost.localdomain (TNS V1-V3)
client details:
O/S info: user: oracle, term: pts/3, ospid: 5065
machine: localhost.localdomain program: sqlplus@localhost.localdomain (TNS V
1-V3)
application name: SQL*Plus, hash value=3669949024
current SQL:
delete d_lock2 where id=1
----- End of information for the OTHER waiting sessions -----
Information for THIS session:
----- Current SQL Statement for this session (sql_id=6zab6b7khav6v) -----
delete d_lock1 where id=1
死锁必须让其中一个事务结束(commit或者rollback)。在应用中常用的方法是kill掉一个死锁,一般是报错的sessioin:
alter system kill session '1,13'
七、 锁的应用:
1. 串行事务:应用场景一般为同一时刻,只能有一个会话操作数据。可以通过悲观锁定(select …for update)来锁定,也可以通过手工锁定。
2. 防止丢失更新:要防止丢失更新首先得保证事务串行,比较不错的一种解决方法是select … for update nowait,让想获得该事务的会话返回错误。如果回话事务执行时间不长,那么可以指定等待时间。如果是只想获得未被其他回话锁定的行锁,那么使用skip locked。
看下语法图:
3. 悲观锁和乐观锁:
悲观锁定:试图更改数据之前,就把数据锁住。常用的就是select...for update.
乐观锁定:将所有锁定延迟到即将执行更新之前。
实现乐观锁方法主要有两种:
使用版本列,如一个number或者timestamp标识,并用触发器维护,每次更新这个列时,触发器负责传递版本列的值,如果不用触发器,可以通过程序控制,查询出当前版本列的值。
使用校验和:将数据要更新的相关列利用各种算法或者函数产生校验和,每次更新之前计算,更新的时候在谓词中将校验和作为条件。常见方法是利用ORA_HASH函数产生校验和
4. 手工锁定的应用:
可以根据业务需求产生不同级别的锁定:
如,产生级别为4的锁,阻止其它回话对行的dml操作。
lock table t in share mode;
...
dml操作;
commit;
5. DBMS_LOCK包的应用
以上均整体自书籍、网络,是自己学习心得。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/29144191/viewspace-1195261/ ,如需转载,请注明出处,否则将追究法律责任。