【问题标题】:Emulating a transaction-safe SEQUENCE in MySQL在 MySQL 中模拟事务安全的 SEQUENCE
【发布时间】:2010-10-22 19:00:29
【问题描述】:

我们经常使用带有 InnoDB 存储引擎和事务的 MySQL,但我们遇到了一个问题:我们需要一种在 MySQL 中模拟 Oracle SEQUENCE 的好方法。要求是: - 并发支持 - 交易安全 - 最大性能(意味着最小化锁和死锁)

我们不在乎某些值是否不会被使用,即序列中的间隙是可以的。有一种简单的方法可以通过创建一个带有计数器的单独 InnoDB 表来实现这一点,但这意味着它将参与事务并引入锁和等待。我正在考虑尝试使用手动锁定的 MyISAM 表,还有其他想法或最佳实践吗?

【问题讨论】:

    标签: mysql transactions


    【解决方案1】:

    如果自动增量不足以满足您的需求,您可以创建一个具有 n 个命名序列的原子序列机制,如下所示:

    创建一个表来存储您的序列:

    CREATE TABLE sequence (
      seq_name varchar(20) unique not null,
      seq_current unsigned int not null
    );
    

    假设您在表中有一行“foo”,您可以像这样自动获取下一个序列 id:

    UPDATE sequence SET seq_current = (@next := seq_current + 1) WHERE seq_name = 'foo';
    SELECT @next;
    

    不需要锁。两条语句都需要在同一个会话中执行,这样局部变量@next才真正在select发生时定义。

    【讨论】:

    • 谢谢 - 毕竟这或多或少是我们想出的 - 几乎完全一样的存储过程。
    • 如果该记录不存在,请使用此查询。 INSERT INTO sequence (seq_name, seq_current) VALUES ('foo', (@next := 1)) ON DUPLICATE KEY UPDATE seq_current = (@next := seq_current + 1); 然后拨打SELECT @next;
    • 您可能希望将 iwat 的评论与 qu1j0t3 的答案结合起来:INSERT INTO sequences2 (seq_name, seq_current) VALUES ('foo', LAST_INSERT_ID(1)) ON DUPLICATE KEY UPDATE seq_current = LAST_INSERT_ID(seq_current + 1); 然后调用 SELECT LAST_INSERT_ID();,或者如果您使用的是应用程序驱动程序,请使用内置的自动增量调用(例如Statement#getGeneratedKeys 在 jdbc 中)
    【解决方案2】:

    MySQL manual 中给出了正确的方法:

    UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
    SELECT LAST_INSERT_ID();
    

    【讨论】:

    • 我看不到这里的逻辑。 LAST_INSERT_ID 仅对自动递增字段有意义。但如果它已经是自动递增的,为什么还需要手动递增呢?
    • 如果不手动设置,更新语句对 LAST_INSERT_ID 没有影响。这可以用作在不使用单独的用户定义变量的情况下从更新语句中获取数据的技巧。
    • 这种方法是正确的,@devios 评论具有误导性。 LAST_INSERT_ID(expr) 与自增无关。
    【解决方案3】:

    我们是一家高交易游戏公司,需要此类解决方案来满足我们的需求。 Oracle序列的特点之一就是增量值也可以设置。

    解决方案使用DUPLICATE KEY

    CREATE TABLE sequences (
      id BIGINT DEFAULT 1,
      name CHAR(20),
      increment TINYINT,
      UNIQUE KEY(name)
    );
    

    获取下一个索引:

    用存储过程或函数sp_seq_next_val(VARCHAR)抽象以下内容:

    INSERT INTO sequences (name) VALUES ("user_id") ON DUPLICATE KEY UPDATE id = id + increment;<br/>
    SELECT id FROM sequences WHERE name = "user_id";
    

    【讨论】:

    • 嗯,很有趣,谢谢指点。在 MySQL 中使用 INSERT 时 ON DUPLICATE KEY 总是原子的吗?
    • @Michael:是的,ON DUPLICATE KEY 是原子的。
    【解决方案4】:

    表上的 MySQL Identity 列不会处理这个吗?

    创建表表名 ( id INTEGER AUTO_INCREMENT 主键 )

    或者您是否希望将其用于其他用途,而不仅仅是插入到另一个表中?

    如果您也在使用过程语言(而不仅仅是 SQL)进行编写,那么另一个选项是创建一个包含单个整数(或长整数)值的表和一个锁定它的存储过程,从它,在返回值之前增加它并解锁它。

    (注意 - 在返回值之前始终递增 - 如果出现错误,它会最大限度地增加不重复的机会 - 或者将整个事物包装在事务中。)

    然后,您将独立于您的主插入/更新调用它(因此它不会被调用机制自动创建的任何事务中捕获),然后将其作为参数传递到您想要使用它的任何地方。

    因为它独立于您正在做的其他事情,所以它应该很快并且避免锁定问题。即使您确实看到了由锁定引起的错误(除非您使数据库过载),您也可以第二次/第三次调用它。

    【讨论】:

    • 谢谢,您的第二种方法可能或多或少是我想到的,细节帮助很大。关于什么存储引擎最适合这种表的任何 cmets?
    • 我对 MySQL 不太了解,所以我不能就此提出建议(我在 SQL Server 上做过)。我想说的是,我认为它没有那么复杂,所以最好的方法可能是使用你正在使用的任何东西来处理其余的东西,并避免增加复杂性。
    猜你喜欢
    • 1970-01-01
    • 2023-03-17
    • 2014-10-28
    • 2022-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-25
    相关资源
    最近更新 更多