在不同隔离级别下可能发生的现象
在一定的隔离级别下,读取数据的程序可能比预期的多或少地检索行。

在任何隔离级别下都可能出现的幻影行
SQL事务 T1 读取满足表中某些搜索条件的一组行。 SQL事务 T2 随后执行SQL语句,在表中生成一个或多个新行,这些新行也满足SQL事务 T1 使用的搜索条件。 如果 T1 在相同的搜索条件下重复读取原始数据,则 T1 会收到一组不同的行。
这种现象可能发生在未承诺读取(UR)、光标稳定性(CS)、读取稳定性(RS)或可重复读取(RR)隔离中。
当表格中存在多列索引,且以下活动同时发生时,就会出现幻行:
- 一个事务执行一个查询,该查询根据表的第一列和第三列索引选择行。 查询使用索引扫描。
- 另一笔交易更新了表中第二列索引的值。 查询尚未选择相应的行。 更新导致更新行的索引键值移动到索引扫描已经通过的点。


假设表EMP_INFO定义如下:
CREATE TABLE EMP_INFO (
WORKDEPT CHAR(3) NOT NULL,
STATE CHAR(2),
JOB CHAR(8),
LASTNAME VARCHAR(15),
FIRSTNME VARCHAR(12));
在表 EMP_INFO 的前三列中定义了索引 EMP_INFO_IX:
CREATE INDEX EMP_INFO_IX ON EMP_INFO(WORKDEPT, STATE, JOB);
表 EMP_INFO 包含如下行。 行以索引顺序显示。 这是使用索引扫描读取行的顺序。
| WORKDEPT | STATE | JOB | LASTNAME | FIRSTNME |
|---|---|---|---|---|
| A00 | 加利福尼亚州 | PRES | HAAS | CHRISTINE |
| A00 | NY | SALESREP | HEMMINGER | DIAN |
| A00 | OH | SALESREP | 卢切斯 | VINCENZO |
| A00 | PA | SALESREP | O'CONNELL | SEAN |
SELECT FIRSTNME, LASTNAME FROM EMP_INFO WHERE WORKDEPT = 'A00' AND JOB='SALESREP';执行事务 T1 的包与隔离可重复读取(RR)绑定。 查询使用索引 EMP_INFO_IX 来检索行。
会发生以下操作:
- 选择操作读取索引的第二个键值(' A00 '、'NY'、'SALESREP'),并从表的第二行中检索数据。 选择操作还会在该表行上锁定带有RR隔离的锁:
WORKDEPT STATE JOB FIRSTNME LASTNAME A00 NY SALESREP DIAN HEMMINGER - 同时,事务 T2 将姓氏列值为O'CONNELL的行的状态列值更新为AK:
UPDATE EMP_INFO SET STATE ='AK' WHERE LASTNAME='O''CONNELL'; - 更新操作将索引 EMP_INFO_IX 中的键顺序更改为:
WORKDEPT STATE JOB A00 AK SALESREP A00 加利福尼亚州 PRES A00 NY SALESREP A00 OH SALESREP - 选择操作继续使用更新的索引顺序检索行:
WORKDEPT STATE JOB A00 OH SALESREP 姓氏列值为O'CONNELL的行是幻行。 结果集中不包含该行,因为索引扫描已经通过了该行索引键值的新位置。
- 如果再次执行查询,且没有影响索引键值顺序的更新,则将返回所有行。


在上面的示例中,更新操作将索引键移动到被选择操作锁定且具有RR隔离的第一行之前的位置。 因此,更新操作能够在选择操作仍在执行时成功运行。
为了避免幻行问题,选择操作需要使用不受更新操作影响的索引。

未提交读取、光标稳定性或读取稳定性隔离时可能出现的幻像行
SQL事务 T1 读取满足表中某些搜索条件的一组行。 SQL事务 T2 随后执行SQL语句,在表中生成一个或多个新行,这些新行也满足SQL事务 T1 使用的搜索条件。 如果 T1 在相同的搜索条件下重复读取原始数据,则 T1 会收到一组不同的行。
这种现象可能发生在未承诺读取(UR)、光标稳定性(CS)或读取稳定性(RS)隔离中。
当表格中存在多列索引,且以下活动同时发生时,就会出现幻行:
- 一次事务执行一个查询,该查询根据表中的第一个索引列选择行。 查询使用索引扫描。
- 另一笔交易更新了表中第二列索引的值。 查询尚未选择相应的行。 更新导致更新行的索引键值移动到索引扫描已经通过的点。
在这种情况下,查询会错过更新的行。
示例 :假设表 EMP_INFO 定义如下:
CREATE TABLE EMP_INFO (
WORKDEPT CHAR(3) NOT NULL,
LASTNAME VARCHAR(15),
FIRSTNME VARCHAR(12),
JOB CHAR(8));
在表 EMP_INFO 的前两列中定义了索引 EMP_INFO_IX:
CREATE INDEX EMP_INFO_IX ON EMP_INFO(WORKDEPT, LASTNAME);
表 EMP_INFO 包含如下行。 行以索引顺序显示。 这是使用索引扫描读取行的顺序。
| WORKDEPT | LASTNAME | FIRSTNME | JOB |
|---|---|---|---|
| A00 | HAAS | CHRISTINE | PRES |
| A00 | HEMMINGER | DIAN | SALESREP |
| A00 | 卢切斯 | VINCENZO | SALESREP |
| A00 | O'CONNELL | SEAN | 职员 |
| A00 | 奥兰多 | 格雷格 | 职员 |
| B01 | THOMPSON | MICHAEL | MANAGER |
| C01 | KWAN | SALLY | MANAGER |
| C01 | NATZ | Kim | ANALYST |
| C01 | NICHOLLS | HEATHER | ANALYST |
| C01 | QUINTANA | DOLORES | ANALYST |
| … |
执行事务的包 T1 和 T2 与RS隔离绑定。
SELECT FIRSTNME, LASTNAME FROM EMP_INFO WHERE WORKDEPT = 'A00';查询使用索引 EMP_INFO_IX 来检索行。会发生以下操作:
- 选择操作读取索引的第一个键值(' A00 '、'HAAS'),并检索第一行:
FIRSTNME LASTNAME CHRISTINE HAAS - 与此同时,事务处理系统 T2 将员工奥康奈尔的姓氏更新为康纳利:
UPDATE EMP_INFO SET LASTNAME='CONNELLY' WHERE LASTNAME='O''CONNELL';更新操作还使用索引 EMP_INFO_IX。
- 更新操作将索引 EMP_INFO_IX 中的键顺序更改为:
('A00','CONNELLY') ('A00','HAAS') ('A00','HEMMINGER') ('A00','LUCCHESI') ('A00','ORLANDO') … - 选择操作继续使用更新的索引顺序检索行:
FIRSTNME LASTNAME DIAN HEMMINGER VINCENZO 卢切斯 格雷格 奥兰多 肖恩·康纳利的这一排是幽灵排。 结果集中不包含该行,因为索引扫描已经通过了该行索引键值的新位置。
如果再次执行查询,且没有影响索引键值顺序的更新,则将返回所有行:
FIRSTNME LASTNAME SEAN 康纳利 CHRISTINE HAAS DIAN HEMMINGER VINCENZO 卢切斯 格雷格 奥兰多
脏读与未承诺的读隔离
SQL事务 T1 修改了一行。 在 T1 执行提交操作之前,SQL事务 T2 读取该行。 如果 T1 执行回滚操作,则 T2 将读取从未提交的一行,因此可以认为该行从未存在过。
这种现象可能发生在未承诺读取(UR)隔离中。
示例 :假设表 EMP_INFO 定义如前例所示,且包含相同的数据。
执行事务的包 T1 和 T2 与UR隔离绑定。
会发生以下操作:
- 交易 T1 将员工 O'Connell 的姓氏更新为 Connelly:
UPDATE EMP_INFO SET LASTNAME='CONNELLY' WHERE LASTNAME='O''CONNELL'; - 事务 T2 执行以下查询:
SELECT FIRSTNME, LASTNAME FROM EMP_INFO WHERE WORKDEPT = 'A00';返回以下几行:
FIRSTNME LASTNAME SEAN 康纳利 CHRISTINE HAAS DIAN HEMMINGER VINCENZO 卢切斯 格雷格 奥兰多 - 事务 T1 执行回滚操作,将更新语句恢复为:
UPDATE EMP_INFO SET LASTNAME='CONNELLY' WHERE LASTNAME='O''CONNELL';上一步的结果不再有效,因为不再有姓氏为“CONNELLY”的行。
行不能以未承诺的读取或光标稳定性隔离进行重读
SQL事务 T1 读取一行。 SQL事务 T2 随后修改或删除该行并执行提交操作。 如果 T1 试图重新读取该行,则 T1 可能会收到修改后的值,或者发现该行已被删除。
这种现象可能发生在未提交读取(UR)或光标稳定性(CS)隔离中。
示例 :假设表 EMP_INFO 定义如第一个示例所示,且包含相同的数据。
执行事务的包 T1 和 T2 与CS隔离绑定。
会发生以下操作:
- 事务 T1 执行以下查询:
SELECT FIRSTNME, LASTNAME, JOB FROM EMP_INFO WHERE LASTNAME = 'HAAS';返回以下行:
FIRSTNME LASTNAME JOB CHRISTINE HAAS PRES - 事务 T2 将Christine Haas的工作从总裁更新为首席执行官,并提交更新:
UPDATE EMP_INFO SET JOB='CEO' WHERE LASTNAME='HAAS' AND FIRSTNME='CHRISTINE'; COMMIT; - 事务 T1 再次执行以下查询:
SELECT FIRSTNME, LASTNAME, JOB FROM EMP_INFO WHERE LASTNAME = 'HAAS';返回的下一行与之前通过同一查询返回的行不同:
FIRSTNME LASTNAME JOB CHRISTINE HAAS 首席执行官