注意:此答案假定您是允许行级锁定的 InnoDB,而不是需要表锁定的 MyISAM。
对于这种情况,您可以使用事务和读/写锁。您需要的具体细节因情况而异,如果不知道您的架构以及您担心什么数据完整性,我无法回答这个问题,所以我会给您一个一般性的解释。
可以在您不打算写入的行上获取读锁,但在事务期间不得更改。可以在您打算在将来某个时间点更改的行上获取写锁。事务是多个操作的序列,它们以全有或全无的方式应用于数据库。
作为一个例子,我们假设如下:
- 您有 3 个表:table_A、table_B、table_C
- 客户端 1 正在执行的操作对 table_A 进行更新,然后对
table_B。
- 客户端 2 可能正在更新任何表。
- 您需要在所有 3 个表之间保持一定的数据一致性。
你会这样做:
// This makes it so that each operation is not automatically commited (saved)
// It implicitly makes all sequences of operations into transactions
execute("set autocommit=0");
// This gets you some data from table_B and also gets a read lock to prevent that data from changing
result = execute("SELECT * FROM `table_B` WHERE `condition` = 1 LOCK IN SHARE MODE");
// This gets some data from table_C and gets a write lock to prevent the data from changing and allowing you to write to it in the future
result2 = execute("SELECT * FROM `table_C` WHERE `condition` = 1 FOR UPDATE");
// This performs your update to table_A
execute("UPDATE `table_A` SET `value` = 1234 WHERE `condition` = 1");
// This performs your update to table_C
execute("UPDATE `table_C` SET `value` = 4321 WHERE `condition` = 1");
// This saves all of the changes that you made during your transaction and releases all locks
// Note: autocommit is still turned off
execute("COMMIT");
让我们举一个更具体的例子,涉及购买东西。我意识到这可以通过单个更新语句完成,但我这样做是为了说明如何使用事务。
我的桌子是:
items (id int not null primary key, user_id int not null, item_type int not null)
accounts (user_id int not null primary key, balance int not null)
prices (item_type int not null primary key, price int not null)
limits (item_type int not null primary key, max_count int not null)
注意,为了简洁起见,我将跳过输入卫生,不要真的这样做。 (http://xkcd.com/327/)
function purchase(user_id, item_type) {
execute("set autocommit=0");
// I am assuming that price and max_count can be changed but they require consistency with each other hence the read locks
var price = execute("SELECT `price` FROM `prices` WHERE `item_type` = " + item_type + " LOCK IN SHARE MODE")[0].price;
var max_count = execute("SELECT `max_count` FROM `limits` WHERE `item_type` = " + item_type + " LOCK IN SHARE MODE")[0].max_count;
// I need the write lock to prevent double spending
var account = execute("SELECT * FROM `accounts` WHERE `user_id` = " + user_id + " FOR UPDATE")[0];
// I need to guarantee that the user is not over the limit
var count = execute("SELECT count(*) AS `count` FROM `items` WHERE `user_id` = " + user_id + " FOR UPDATE")[0].count;
var new_balance = account.balance - price;
if(count >= max_count || new_balance < 0) {
return false;
}
execute("INSERT INTO `items` (`user_id`, `item_type`) VALUES (" + user_id + ", " + item_type + ")");
execute("UPDATE `accounts` SET `balance` = " + new_balance + " WHERE `user_id` = " + user_id);
execute("COMMIT");
return true;
}
还应注意,您现在必须担心死锁,但这是一个完全独立的主题。