Chinaunix首页 | 论坛 | 博客
  • 博客访问: 90784139
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784140
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784141
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784142
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784143
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784144
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784145
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784146
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784147
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784148
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784139
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784150
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784151
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784152
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784153
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784154
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784155
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784156
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784157
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784158
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784159
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784160
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784161
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784162
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784163
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784154
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784165
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784166
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784167
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784168
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784169
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784170
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784171
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784172
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784173
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784174
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784175
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784176
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784177
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784178
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784169
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784180
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784181
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784182
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784183
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784184
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784185
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784186
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784187
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784188
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784189
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks

DB2 9 应用开发(733 考试)认证指南,第 8 部分: 高级编程(7)-sdccf-ChinaUnix博客
  • 博客访问: 90784190
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-05-31 23:46:17

用户定义的数据类型、并发性考虑因素等等

developerWorks



使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380238) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380237) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380236) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380235) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380234) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380233) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380232) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380231) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380230) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380229) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380228) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380227) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380226) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380225) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380224) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380223) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380222) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380221) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380220) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380219) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380218) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380217) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380216) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380215) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380214) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380213) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380212) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380211) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380210) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380209) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380208) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380207) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380206) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380205) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380204) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380203) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380202) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380201) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380200) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380199) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380198) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380197) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380196) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380195) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380194) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380193) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380192) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380191) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380190) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380189) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380188) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~


使用 DB2 数据库并发性

每个数据库程序员都应该理解数据库并发性是如何工作的。所有 RDBMS 都遵循核心数据库数据完整性原则,但是它们在如何处理锁定、事务和并发性控制方面有很大差异。通过学习更多关于 DB2 并发性的内容,可以避免错误和并发性问题,还可能仅用少量的修改大大提高应用程序性能。

为了避免以后会混淆,先定义一些术语是十分重要的:

  • 隔离级别(Isolation level):DB2 使用隔离级别来维护和控制数据库中数据的完整性。隔离级别定义进程受保护(或隔离)的程度,以防止当它们还在数据库中执行时受到其他应用程序所做修改的影响。隔离级别是由每个应用程序指定的。这意味着,连接同一数据库的多个应用程序可能使用不同的隔离级别。

  • 锁模式(Lock mode):这表示允许锁拥有者执行的访问类型,以及对被锁定对象进行访问的并发用户所允许的访问类型。有时候,这也称作锁的 “状态(state)”。

  • 死锁(Deadlock):当连接同一数据库的两个或更多进程无限期地等待完成事务所需的资源时,就会发生死锁。这种等待状态会一直持续下去,因为每个进程都拥有其他进程所需的资源。

  • 锁升级(Lock escalation):如果由于锁内存空间不足,或者到达了锁分配阈值,同一个表上的多个行锁转换为单个表锁,就是发生了锁升级。





回页首


DB2 支持四种隔离级别。下面列出了这些隔离级别,按照提供最小的数据保护程度到提供最大的数据保护级别依次排列。

  1. 未提交读(Uncommitted Read,UR)。未提交读是最弱的隔离级别,提供最少的数据保护。在使用未提交读时,在工作单元执行过程中可以读取任何行,即使它正被另一应用程序进程修改。这意味着,您的应用程序可能返回由于另一应用程序的事务回滚而不再有效的数据。使用 UR 可能导致读取不正确的数据或陈旧的数据,这就是它有时称作脏读(dirty read) 的原因。

    用法:UR 在大型数据仓库系统中极其有用,在这种系统中不希望由于等待几个行锁而停止操作。UR 常常用于生成报表,这样可以避免受到数据库中锁处理的影响。

  2. 游标稳定性(Cursor Stability,CS)。游标稳定性是默认的和最常用的隔离级别。CS 持有游标当前所在的行上的锁。这确保当读取或更改该行时,另一进程无法修改它。该行将被一直锁定,直到读取新的一行,或直到终止或完成该工作单元。

    如果更新了任何行,那么该锁将持续到提交或回滚该工作单元。CS 隔离级别不返回未提交的数据。

    用法:CS 隔离级别是最常用的,因为它确保不读取当前被锁定的行;它还锁定您正在读取的行。因为这种锁只控制当前处理的行,所以竞争极小且减少了锁定,但是仍然实施了数据保护。许多事务处理系统都使用这个隔离级别。

  3. 读稳定性(Read Stability,RS)。读稳定性隔离级别会锁定应用程序在工作单元(UOW)期间检索的所有行,无论它们是否被更新了。这确保它所使用的所有数据都无法被更改,并保证结果集在 UOW 期间保持不变。RS 级别确保其他应用程序无法修改在工作单元期间返回或使用的数据。

    用法:这个隔离级别提供更高的数据保护,因为由查询返回的所有行在 UOW 期间都被锁定。但是,它极大地增加了被持有的锁的数量,并可能降低并发性。

  4. 可重复读(Repeatable Read,RR)。这是最强的隔离级别,它提供最大程度的数据完整性。它将锁定工作单元期间处理的所有行。这意味着没有在结果集中返回的行也会被锁定,直到提交或终止该工作单元。这会给并发性带来极大的负面影响,因为在使用这个隔离级别时整个表通常都被锁定。但是,如果在一个工作单元期间重复一个查询,那么它确保返回完全相同的结果。使用这个隔离级别的最大缺点就是优化器通常要采用表锁,这实际上会阻止其他应用程序处理被锁定表上的查询。

    用法:如果查询在同一 UOW 中每次执行都必须返回完全相同的结果集,就使用这个隔离级别。





回页首


DB2 的隔离级别系统用于防止数据被其他应用程序修改或访问,从而避免数据完整性问题。在这一节中,将了解一些潜在的数据完整性问题。

当可以读取未提交的数据时可能会出现 “脏读”,因为如果这些数据的修改被回滚,就会导致在数据处理中使用无效数据。

图 5 中说明了这种场景。在这里,更新了一行并更改了 P-name 字段,但是没有提交。然后,另一个使用 UR 隔离级别的应用程序执行 SELECT 语句。因为 UR 忽略行上的锁,所以它将返回未提交的值。但是,更新事务在被提交之前被回滚。因而,SELECT 语句返回错误的值。



读取了未提交的数据

如果一个查询在同一工作单元中返回不同的结果集,就是出现了 “不可重复读”。图 6 中说明了这种场景。假设应用程序 A 查找从 Dallas 到 Honolulu 的所有航班,应用程序 B 在 flight 表中删除了一个航班,因为这个航班被取消了。如果应用程序 A 在它的事务中再次执行同样的查询,就不会得到完全相同的数据集:其中不包含取消的那个航班。因为在应用程序 A 运行其事务时,允许应用程序 B 更改这个表,这个查询将是不可重复的。



不可重复读示例

如果应用程序在一个事务中执行同一 SQL 语句多次,而且后续执行会返回附加行,就是发生了 “幻像读”。

图 7 中说明了这种场景。假设应用程序 A 查询 512 航班的所有空闲座位,发现只有一个座位空着。在此之后,由于一位乘客取消了飞机票,应用程序 B 取消了这个航班上一个座位的预订。如果应用程序 A 在这个事务中再次执行同一查询,那么它不会得到完全相同的数据集 —— 会出现新的座位。



幻像读





回页首


并非所有应用程序都会受到前一节所列问题的损害。实际上,在特定的应用程序中其中某些并发场景可能是可以接受的。但是,一定要了解隔离级别之间的差异,并在满足自己应用程序需要的情况下,选择限制最少的隔离级别。如果认为应用程序中可能发生这些场景,那么应该知道哪些隔离级别可以避免它们。表 1 可以帮助您做出决定。




隔离级别 读取未提交数据 不可重复读 幻像读
可重复读 不可能 不可能 不可能
读稳定性 不可能 不可能 可能
游标稳定性 不可能 可能 可能
未提交读 可能 可能 可能





回页首


DB2 通过称为 LOCKLIST 的内存区域来跟踪应用程序进程所持有的所有锁。每个锁占用少量的内存,所以数据库很少会用完 LOCKLIST 内存。但是,如果用完了内存,一些锁将会被升级。这涉及到将一个表上的许多行锁换成单个表锁。表锁可能导致并发性问题,因为应用程序锁定整个表,使其他应用程序无法更新这个表中的任何行。



锁升级示意图

图 8 中,上图说明了应用程序超出允许它具有的总的锁内存百分比(MAXLOCKS 值)的情况。因此,数据库执行锁升级,将应用程序的锁内存百分比降低到 MAXLOCKS 指定的百分比之下。下图说明了更为常见的情况:到达总的锁内存限制,因此执行锁升级来释放锁内存。

锁升级问题可以通过增加 LOCKLISTMAXLOCKS 数据库参数的大小来解决。但是,如果仍然遇到锁定问题,很可能应该检查是否未能提交事务,从而未释放已更新行上的锁。





回页首


尽管 DB2 使用许多不同类型的锁,但是理解锁定是如何工作的以及了解最常用的锁类型是最重要的。锁模式(lock mode) 是允许锁所有者执行的访问类型,以及允许并发应用程序在被锁定对象上执行的访问类型。

两种主要的锁类型是排他(exclusive)共享(share) 锁。共享锁(或称为 S 锁)是在要进行读操作的行上设置的;当读取该行时,它们防止该行被删除或更改。一旦不再读该行了,就释放这个锁,可以对它加以更改。排他锁(或称为 X 锁)是在被更新、删除或插入的行上设置的。排他锁防止该行被读取或更新,直到提交或回滚该事务为止。这样的话,另一个应用程序就不会读取未提交的数据。请记住,隔离级别也用来控制持有锁多长时间以及锁的工作方式。例如,在未提交读隔离级别下运行的应用程序能够读取设置了排他锁的行。

所有的锁类型都会被持有它们的应用程序忽略,因为这些锁只用于防止其他应用程序修改或访问由持有该锁的应用程序所修改的数据。





回页首


锁模式以不同的方式相互进行交互。可以在 DB2 文档中找到锁类型的完整列表。下面的列表包括最常用的锁类型的精确定义以及每种类型在何时出现的描述。

以下列表总结了最重要的锁类型。

  • Share:出现在使用 SELECT 语句读取数据期间。
  • Update:出现在使用游标和 FOR UPDATE 子句更新行时。
  • Exclusive:出现在插入、更新或删除一行时。

  • Intent none:持有它的应用程序只能读取数据,包括未提交的数据;其他应用程序可以读取和更新该对象。
  • Intent share:持有它的应用程序只能读取数据,其他应用程序可以读取和更新该对象。
  • Intent exclusive:该锁的所有者可以读取和更新数据,其他应用程序也可以。
  • Update:该应用程序可以进行更新。其他应用程序可以读取数据,但是不能试图更新它。
  • Exclusive:该锁的所有者可以读取和修改该对象。如果其他应用程序使用的是未提交读隔离级别,才可以读取被锁定的数据。

图 9 总结了不同锁类型之间的兼容性。关于这里使用的缩写的更多信息,请参阅 DB2 文档。



锁兼容性表





回页首


“死锁” 是一种并发性问题,当两个应用程序相互持有对方所需的资源上的锁时,就会发生死锁。这两个应用程序将无限期地等待另一个应用程序释放所需资源上的锁,这会导致应用程序挂起。

当到达指定的锁超时值时,等待锁的应用程序放弃并回滚其事务,这时死锁就可以结束。如果 DB2 死锁守护进程检测出死锁,也可以解除死锁,这个守护进程是一个按指定的时间间隔运行并检查系统中是否存在死锁的异步进程。死锁守护进程会选择回滚哪个进程,这会导致释放这个事务的锁,让死锁涉及的另一个应用程序能够获得锁并完成其事务。

通过将数据库锁超时参数(LOCKTIMEOUT)或 CURRENT LOCK TIMEOUT 特殊寄存器设置为更短的间隔,可以减少遇到的死锁数量。这确保应用程序不会等待太长的时间,但是这仍然可能无法完全解决死锁问题。此外,还可以减少死锁检查守护进程被激活的时间间隔(通过 DLCHKTIME 数据库参数)来提高检查死锁的频率。不过,一般不推荐这样做,因为如果进行了大量的锁定,那么该守护进程就会消耗大量的 CPU 时间。





回页首


通常,可以通过稍微更改应用程序处理数据和事务的方式来完全避免死锁或锁等待。

锁竞争的最大原因之一就是未能提交事务,包括只读事务。如果事务未提交或回滚,它的锁将继续保留在它所处理的所有对象上。随着时间的推移,这可能导致许多与其他应用程序的竞争。因此一旦完成一个业务工作单元,就应该发出 COMMIT 语句。如果一组 SQL 语句不依赖于前一组的结果,而且一组 SQL 语句的失败将不影响另一组的结果,那么它们很可能为两个独立的事务。通过在它们之间放置 COMMIT 语句,释放第一组 SQL 语句中的锁,这可以减小其他应用程序必须等待它们的可能性。

如果一个应用程序试图锁定已被另一个应用程序锁定的资源,就会发生锁竞争。有时候,锁竞争和它们导致的锁等待是无法避免的。但是,有时候锁竞争是由于 DB2 在太多的行中进行读取导致的,而这由于表上不存在索引或者请求数据的应用程序未使用索引导致的。缺少适当的索引会导致 DB2 执行表扫描,这意味着 DB2 必须等待已被另一个应用程序锁定的行,即使这些行未包含在该事务中。有时候,难以检测应用程序的设计是否可以避免数据竞争,只是却缺乏应用程序需要的索引。

锁问题的发生通常是因为持有排他锁的事务没有提交。如果遇到锁定问题,应该查找长期运行的已修改了数据但却未提交的事务。然后,考虑是否可以将那些事务分成多个事务,并在小型事务之间执行 COMMIT 语句来释放一些锁。





回页首


为了提高并发性,DB2 现在在某些情况下允许将 CS 或 RS 隔离扫描的行锁延迟到一个已知满足查询谓词的记录。在默认情况下,当在表或索引扫描期间执行行锁定时,DB2 在确定该行是否满足该查询之前,锁定所扫描的每一行。为了提高扫描的并发性,可以将行锁定延迟到 DB2 确定了满足查询的行。另外,还可以让扫描无条件地跳过已删除或插入但未提交的记录。

为了利用这些功能,可以启用 DB2_EVALUNCOMMITTEDDB2_SKIPINSERTEDDB2_SKIPDELETED 注册表变量。更多信息请参考 参考资料 一节中的文章 “Lock avoidance in DB2 UDB V8”。

阅读(380187) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~