【问题标题】:what to do with concurrency-risky tasks?如何处理并发风险的任务?
【发布时间】:2014-05-22 15:15:38
【问题描述】:

我正在为此寻找解决方案:

function foo()
{
  client 1 executes an update/delete
  // client 2 calls foo() and breaks data integrity
  client 1 executes an update/delete
}

如何用 mysql 解决这个问题?我正在使用 myisam 表,但我也对 innoDB 解决方案感兴趣

【问题讨论】:

  • 如果您能提供一些架构和您担心的数据完整性示例,将会很有帮助。这样可以推荐更多具体的解决方案。
  • 查询 1 验证父 ID,查询 2 引用另一行中的父 ID...在两者之间,第二个任务删除父 ID 的行...

标签: mysql concurrency innodb myisam


【解决方案1】:

注意:此答案假定您是允许行级锁定的 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;
}

还应注意,您现在必须担心死锁,但这是一个完全独立的主题。

【讨论】:

  • 我不使用 MyISAM,也不愿意发表评论。
  • 我的项目经理说 innodb 几乎不可能在 Windows 上使用(错误和崩溃)所以我坚持使用 myisam :-(
  • IMO 迁移到 MSSQL 比继续使用 MyISAM 更好
猜你喜欢
  • 1970-01-01
  • 2020-06-02
  • 2015-07-18
  • 1970-01-01
  • 1970-01-01
  • 2013-08-04
  • 1970-01-01
  • 1970-01-01
  • 2017-09-24
相关资源
最近更新 更多