【问题标题】:MySQL INSERT ....ON DUPLICATE UPDATE - Adds one to the autoincrementMySQL INSERT ....ON DUPLICATE UPDATE - 向自动增量添加一个
【发布时间】:2011-10-28 14:35:40
【问题描述】:

我用一个简单的计数器跟踪所有访问我的 http_user_agents。 下面在数据库中插入 http_user_agent,该字段不区分大小写并且是唯一的。因此,当我们尝试插入它并找到一个 DUPLICATE KEY 时,它会将 hits 字段加 1。

问题是即使我们没有插入字段,我的自动增量字段仍然会增加。我怎样才能防止这种情况发生?

$sql = "INSERT INTO `db_agency_cloud`.`tblRefHttpUsersAgent` SET `http_users_agent` = :UsersAgent, `created_ts` = NOW() ON DUPLICATE KEY UPDATE `hits` = `hits` + 1";

这是表格结构:

CREATE TABLE `tblRefHttpUsersAgent`
(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`http_users_agent` varchar(255) NOT NULL,
`hits` int(20) unsigned NOT NULL DEFAULT '1',
`created_ts` datetime NOT NULL,
`activity_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `http_users_agent` (`http_users_agent`)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

【问题讨论】:

标签: php mysql innodb


【解决方案1】:

你可以先计算最大插入行数加1,

(SELECT MAX(`id`)+1 FROM `tblRefHttpUsersAgent`)

然后使用一些变量SET @NEW_IDPREPARE / EXECUTE 语句更改表AUTO_INCREMENT

here 是针对同一问题的简单解决方案,如果您喜欢自己特定问题的解决方案,以下是最终版本:

    $sql = 'SET @NEW_AI = (SELECT MAX(`id`)+1 FROM `tblRefHttpUsersAgent`);
                    SET @ALTER_SQL = CONCAT("ALTER TABLE `tblRefHttpUsersAgent` AUTO_INCREMENT =", @NEW_AI);
                    PREPARE NEWSQL FROM @ALTER_SQL;
                    EXECUTE NEWSQL;';

    $sql .= 'INSERT INTO `db_agency_cloud`.`tblRefHttpUsersAgent` SET `http_users_agent` = :UsersAgent, `created_ts` = NOW() ON DUPLICATE KEY UPDATE `hits` = `hits` + 1';

【讨论】:

    【解决方案2】:

    INSERT ... ON DUPLICATE KEY UPDATE 被描述为“混合模式插入”,用于 InnoDB 的 AUTO_INCREMENT 处理。混合模式插入基本上是已知最大所需AUTO_INCREMENT值的数量,但实际需要的数量未知。

    默认情况下,混合模式插入得到特殊处理,如MySQL docs 中所述:

    ...对于“混合模式插入”... InnoDB 将 分配比要分配的行数更多的自增值 插入。但是,所有自动分配的值都是连续的 生成(并因此高于)由 最近执行的先前语句。 “多余”的数字是 丢了。

    如果您使用 InnoDB,您的替代方案是:

    1. 避免INSERT ... ON DUPLICATE KEY UPDATE
    2. innodb_autoinc_lock_mode 参数设置为0,用于“传统”自动增量锁定模式,这保证所有INSERT 语句将为AUTO_INCREMENT 列分配连续值。但是,这是通过在语句期间锁定来实现的,因此会降低与此设置相关的性能。
    3. (推荐)忽略AUTO_INCREMENT 列中的空白。

    注意:AUTO_INCREMENT 在 MyISAM 下的处理方式完全不同,不会出现这种行为。

    【讨论】:

    • 尝试在运行时设置 innodb_autoinc_lock_mode 给了我错误:SQLSTATE[HY000]: General error: 1238 Variable 'innodb_autoinc_lock_mode' is a read only variable',考虑到我需要这个用于批量 UPSERT,因为单个插入非常慢。我不能使用选项 1、2 或 3,因为这些间隙会将 autoinc id 烧成疯狂的数字。您能提出其他解决方案吗?
    • 如果我使用 PostgreSQL,会在自动增量字段中观察到类似的差距吗?
    • 感谢您的精彩解释。尽管您推荐了第三个选项,但不适用于我的情况,即我们尝试在不同的事务中同时执行多个INSERT ... ON DUPLICATE KEY UPDATE,这会导致不断的死锁。记住我的两美分。
    【解决方案3】:

    不幸的是,如果您不想影响自动增量 ID,则解决方案是在应用程序级别。首先执行SELECT 并计算结果行数。如果 0 结果,INSERT 数据。如果大于 0,则UPDATE 该行。

    【讨论】:

    • 是的,但我不想对数据库进行两次调用。我希望有一个跳过这个的解决方案。我在想也许是一个 if 语句。然后在数据库级别检查它是否应该插入或更新记录。
    【解决方案4】:

    存储引擎必须在插入行之前增加AUTO_INCREMENT 的值。它不知道此时插入是否会失败。它不能简单地回滚增量,因为可能同时在其他连接上发生其他插入。这是正常行为,而不是您应该(或可以)改变的事情。 AUTO_INCREMENT 的目的是提供唯一标识符,而不是完整的数字序列。

    【讨论】:

    • 是的,但我不想对数据库进行两次调用。我希望有一个跳过这个的解决方案。我在想也许是一个 if 语句。然后在数据库级别检查它是否应该插入或更新记录。
    • +1:很有趣!好的,我知道为什么会发生这种情况(感谢您的回答);不过,这当然看起来很不幸。它根本不直观,我什至认为它是一个错误(无论是在 SQL 标准中还是在 MySQL 中)......即使它总是被解析为 WONTFIX
    • dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html 让我有点困惑。它似乎没有说任何与更新AUTO_INCREMENT 计数器或其他内容相关的内容,但出于好奇,关于使LAST_INSERT_ID() 有用的最后一点让我完全划清了界限。我不知道它在说什么。
    • @Tomalak:那东西是在谈论 LAST_INSERT_ID() 在你传递一个参数时所做的事情:它返回那个值,加上它把那个值设置为 next 调用到LAST_INSERT_ID() 应该返回。因此,在该查询中包含LAST_INSERT_ID() 调用会强制对LAST_INSERT_ID() 的后续调用返回受语句影响的行的ID,无论是否插入了新行(在没有那个“hack”的情况下已经可以工作)或旧行已更新。
    猜你喜欢
    • 2011-08-09
    • 2019-05-21
    • 1970-01-01
    • 2023-02-06
    • 2010-10-07
    • 1970-01-01
    • 1970-01-01
    • 2017-04-14
    • 1970-01-01
    相关资源
    最近更新 更多