教程:脏读中的示例演示了第一类不一致(即脏读)。在该示例中,会计对零售额进行了计算,而销售经理正在更新一个价格。会计的计算使用了销售经理已经输入但正在进行改正的错误信息。
以下示例演示了另一类不一致:非可重复读取。在本示例中,假设两个相同的雇员角色,且他们都并发使用 SQL Anywhere 示例数据库。销售经理想要给塑料太阳帽定一个新的销售价格。会计想要核实某些出现在最近一份订单上的商品的价格。
本例开始时两个连接都处于隔离级别 1,而不是隔离级别 0(随 SQL Anywhere 提供的 SQL Anywhere 示例数据库的缺省隔离级别)。将隔离级别设置为 1 可以消除上一教程中演示的不一致类型(即脏读)。
要按照此教程正常工作,则一定不要选择 Interactive SQL 中的 [自动释放数据库锁] 选项([工具] » [选项] » [SQL Anywhere])。
启动 Interactive SQL。
在 [连接] 窗口中,以销售经理的身份连接到 SQL Anywhere 示例数据库:
在 [ODBC 数据源名称] 字段中,选择 [SQL Anywhere 11 Demo]。
单击 [高级] 选项卡,在 [ConnectionName] 字段中键入 Sales Manager。
单击 [确定]。
再启动一个 Interactive SQL 的实例。
在 [连接] 窗口中,以会计的身份连接到 SQL Anywhere 示例数据库:
在 [ODBC 数据源名称] 字段中,选择 [SQL Anywhere 11 Demo]。
单击 [高级] 选项卡,在 [ConnectionName] 字段中键入 Accountant。
单击 [确定]。
执行以下命令,将会计的连接的隔离级别设置为 1。
SET TEMPORARY OPTION isolation_level = 1; |
在销售经理的窗口中执行以下命令,将隔离级别设置为 1:
SET TEMPORARY OPTION isolation_level = 1; |
会计决定列出太阳帽的价格。作为会计,执行以下命令:
SELECT ID, Name, UnitPrice FROM Products; |
ID | Name | UnitPrice |
---|---|---|
300 | Tee Shirt | 9.00 |
301 | Tee Shirt | 14.00 |
302 | Tee Shirt | 14.00 |
400 | Baseball Cap | 9.00 |
401 | Baseball Cap | 10.00 |
500 | Visor | 7.00 |
501 | Visor | 7.00 |
... | ... | ... |
销售经理决定为塑料太阳帽定一个新的销售价格。作为销售经理,执行以下命令:
SELECT ID, Name, UnitPrice FROM Products WHERE Name = 'Visor'; UPDATE Products SET UnitPrice = 5.95 WHERE ID = 501; COMMIT; SELECT ID, Name, UnitPrice FROM Products WHERE Name = 'Visor'; |
ID | Name | UnitPrice |
---|---|---|
500 | Visor | 7.00 |
501 | Visor | 5.95 |
比较 [Sales Manager] 窗口中太阳帽的价格和 [Accountant] 窗口中相同太阳帽的价格。会计再次执行 SELECT 语句,并将看到销售经理的新销售价格。
SELECT ID, Name, UnitPrice FROM Products; |
ID | Name | UnitPrice |
---|---|---|
300 | Tee Shirt | 9.00 |
301 | Tee Shirt | 14.00 |
302 | Tee Shirt | 14.00 |
400 | Baseball Cap | 9.00 |
401 | Baseball Cap | 10.00 |
500 | Visor | 7.00 |
501 | Visor | 5.95 |
... | ... | ... |
这种不一致被称作非可重复读取,因为如果会计在同一事务 中再次执行相同的 SELECT 命令,则不会得到相同的结果。
当然,如果会计已经完成了他的事务,例如,在再次使用 SELECT 之前发出了 COMMIT 或 ROLLBACK 命令,情况则有所不同。该数据库可供多个用户同时使用,而且完全允许某人在会计执行事务之前或之后更改值。结果中的变化仅仅因为发生在执行其事务的过程中才不一致。这种情况将导致调度不可序列化。
会计注意到了这种情况,并决定从现在开始不希望看到价格发生更改。隔离级别 2 消除了非可重复读取。作为会计,执行以下语句:
SET TEMPORARY OPTION isolation_level = 2; SELECT ID, Name, UnitPrice FROM Products; |
销售经理决定最好将塑料太阳帽的销售推迟到下个星期,这样,她就不必为预计明天会收到的一个大订单报那个较低的价格。在她的窗口中,尝试执行以下语句。该命令将开始执行,然后她的窗口将呈现冻结状态。
UPDATE Products SET UnitPrice = 7.00 WHERE ID = 501; |
数据库服务器在隔离级别 2 上必须确保可重复读取。由于会计正在使用隔离级别 2,因此数据库服务器在会计读取的 Products 表的每一行上都放置一个读锁定。当销售经理尝试将价格更改回原来的值时,她的事务必须在 Products 表中包含塑料太阳帽的那一行上获取一个写锁定。由于写锁定是独占的,因此她的事务必须等到会计的事务释放其读锁定后才能继续执行。
会计查看完价格后,由于他不希望无意中更改了数据库,因此使用 ROLLBACK 语句完成他的事务。
ROLLBACK; |
数据库服务器执行该语句后,销售经理的事务完成。
ID | Name | UnitPrice |
---|---|---|
500 | Visor | 7.00 |
501 | Visor | 7.00 |
销售经理现在就可以完成她的工作了。她希望提交她所做的更改,恢复到原来的价格。
COMMIT; |
将会计的隔离级别从级别 1 升级到级别 2 后,数据库服务器将在以前未获取任何锁定的位置上使用读锁定。一般来说,各个隔离级别所需的锁类型和处理其它事务持有的锁的方式各不相同。
处于隔离级别 0 时,数据库服务器只需要写锁定。它使用这些锁来确保任何两个事务都无法完成相互冲突的修改。例如,级别 0 的事务在更新或删除一行之前在该行上获取一个写锁定,而插入新行时,这些新行上已经具有写锁定。
级别 0 的事务对它们读取的行不进行任何检查。例如,当一个级别 0 的事务读取一行时,它不会检查其它事务是否已在该行上获取了何种锁。由于不需要进行任何检查,因此级别 0 的事务很快。速度快是以牺牲一致性为代价的。如果读取的行已被其它事务执行了写锁定,就有返回脏数据的危险。
在级别 1 上,事务在读取行之前将检查该行是否已被写锁定。虽然需要多执行一项操作,但这些事务可以确保读取的所有数据都是已提交的数据。请尝试重复第一个教程,将隔离级别设置为 1(而不是 0),然后您会发现,在销售经理更新 T 恤衫价格的事务保持未完成状态时,无法进行会计计算。
当会计将其隔离级别提高到级别 2 后,数据库服务器开始使用读锁定。从这时开始,数据库服务器将在符合会计的选择条件的每一行上为他的事务获取一个读锁定。
在上面的教程中,销售经理的窗口在执行她的 UPDATE 命令时被冻结。数据库服务器开始执行她的命令,之后发现会计的事务已在销售经理需要更改的行上获取了一个读锁定。这时,数据库服务器只是暂停该 UPDATE 命令的执行。在会计使用 ROLLBACK 完成了他的事务之后,数据库服务器自动释放了他的锁定。发现不再有任何阻碍后,数据库服务器将继续执行销售经理的 UPDATE 命令。
一般来说,当一个事务试图在另一个事务持有锁的行上获取独占锁时,或当一个事务试图在另一个事务持有独占锁的行上获取共享锁时,将出现锁定冲突。这个事务必须等待另一个事务完成才能继续。我们说,那个必须等待的事务被另一个事务阻塞。
当数据库服务器发现导致某个事务无法立即继续执行的锁定冲突时,它可以或者暂停执行该事务,或者终止该事务,回退所有更改,并返回一个错误。可以通过设置 blocking 选项来控制其行为。当 blocking 被设置为 On 时,第二个事务将等待,如上面的教程所述。
有关 blocking 选项的详细信息,请参见blocking 选项。
您还可以使用快照隔离帮助您避免阻塞。因为使用快照隔离的事务仅查看已提交的数据,所以会计的事务不会阻塞销售经理的事务。
启动 Interactive SQL。
在 [连接] 窗口中,以销售经理的身份连接到 SQL Anywhere 示例数据库:
在 [ODBC 数据源名称] 字段中,选择 [SQL Anywhere 11 Demo]。
单击 [高级] 选项卡,在 [ConnectionName] 字段中键入 Sales Manager。
单击 [确定]。
再启动一个 Interactive SQL 的实例。
在 [连接] 窗口中,以会计的身份连接到 SQL Anywhere 示例数据库:
在 [ODBC 数据源名称] 字段中,选择 [SQL Anywhere 11 Demo]。
单击 [高级] 选项卡,在 [ConnectionName] 字段中键入 Accountant。
单击 [确定]。
执行以下语句为数据库启用快照隔离,并指定使用快照隔离级别:
SET OPTION PUBLIC.allow_snapshot_isolation = 'On'; SET TEMPORARY OPTION isolation_level = snapshot; |
会计决定列出太阳帽的价格。作为会计,执行以下命令:
SELECT ID, Name, UnitPrice FROM Products ORDER BY ID; |
ID | Name | UnitPrice |
---|---|---|
300 | Tee Shirt | 9.00 |
301 | Tee Shirt | 14.00 |
302 | Tee Shirt | 14.00 |
400 | Baseball Cap | 9.00 |
401 | Baseball Cap | 10.00 |
500 | Visor | 7.00 |
501 | Visor | 7.00 |
... | ... | ... |
销售经理决定为塑料太阳帽定一个新的销售价格。作为销售经理,执行以下命令:
UPDATE Products SET UnitPrice = 5.95 WHERE ID = 501; COMMIT; SELECT ID, Name, UnitPrice FROM Products WHERE Name = 'Visor'; |
会计再次执行他的查询且并未看到价格的变化,这是因为事务使用第一次读取时提交的数据。
SELECT ID, Name, UnitPrice FROM Products; |
作为销售经理,将塑料太阳帽的价格更改回其原来的价格。
UPDATE Products SET UnitPrice = 7.00 WHERE ID = 501; COMMIT; |
数据库服务器不会在 Products 表中会计正在读取的行上放置读锁定,因为会计正在查看销售经理对 Products 表进行任何修改之前获取的已提交数据的快照。
会计查看完价格后,由于他不希望无意中更改了数据库,因此使用 ROLLBACK 语句完成他的事务。
ROLLBACK; |
Copyright © 2009, iAnywhere Solutions, Inc. - SQL Anywhere 11.0.1 |