Углубленный анализ Mybatis с использованием useGeneratedKeys для получения самоинкрементных первичных ключей.

MySQL

Резюме

Мы часто используем useGenerateKeys, чтобы вернуть автоматически увеличивающийся первичный ключ, чтобы избежать еще одного запроса. Он также часто используется при обновлении дубликатов ключей для выполнения insertOrUpdate, чтобы избежать предварительного запроса на вставку/обновление. Это очень круто в использовании, но я часто наступаю на яму, и я не знаю, почему. Эта статья представляет собой глубокий анализ принципа получения автоинкрементного первичного ключа.

вопрос

Сначала выберите два абзаца ошибок в некоторых старых кодах нашей компании.

Массовая вставка избранного пользователя

for (tries = 0; tries < MAX_RETRY; tries++) {
    final int result = collectionMapper.insertCollections(collections);
    if (result == collections.size()) {
        break;
    }
}
if (tries == MAX_RETRY) {
    throw new RuntimeSqlException("Insert collections error");
}
// 依赖数据库生成的collectionid
return collections;

Метод collectionMapper.insertCollections

<insert id="insertCollections" parameterType="list" useGeneratedKeys="true"
        keyProperty="collectionId">
    INSERT INTO collection(
    userid, item
    )
    VALUES
    <foreach collection="list" item="collection" separator=",">
        (#{collection.userId}, #{collection.item})
    </foreach>
    ON DUPLICATE KEY UPDATE
    status = 0
</insert>

Я не знаю, сможете ли вы найти проблему

анализировать

Есть две проблемы

Ошибка оценки результата возвращаемого значения

использоватьon duplicate keyКоличество строк, затронутых пакетным обновлением, отличается от количества вставленных строк. Основная причина совершения этой ошибки — принять ее как должное и не читать документацию. посмотриОфициальная документация сайтанаписано четко

With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values. If you specify the CLIENT_FOUND_ROWS flag to the mysql_real_connect() C API function when connecting to mysqld, the affected-rows value is 1 (not 0) if an existing row is set to its current values.

Есть три возвращаемых значения 0: нет обновления 1: вставить 2. обновить Существует также особый случай: обновить идентичное значение до исходного значения, которое может быть 0 или 1 в зависимости от конфигурации клиента.

Так что это суждение явно неверно

Используйте пакетный параметр userGeneratedKey InsertOrUpdate, чтобы вернуть автоматически увеличивающийся первичный ключ.

Эта проблема возникает, когда при пакетной вставке имеется оператор обновления. Все возвращаемые первичные ключи автоинкремента неверны, почему?

1. Во-первых, давайте посмотрим на описание useGeneratedKey от mybatis.

>This tells MyBatis to use the JDBC getGeneratedKeys method to retrieve keys generated internally by the database (e.g. auto increment fields in RDBMS like MySQL or SQL Server). Default: false.

就是使用JDBC的getGeneratedKeys的方法来获取的。

2. Давайте узнаемСпецификация JDBC

Before version 3.0 of the JDBC API, there was no standard way of retrieving key values from databases that supported auto increment or identity columns. With older JDBC drivers for MySQL, you could always use a MySQL-specific method on the Statement interface, or issue the query SELECT 1528426 after issuing an INSERT to a table that had an AUTO_INCREMENT key. Using the MySQL-specific method call isn't portable, and issuing a SELECT to get the AUTO_INCREMENT key's value requires another round-trip to the database, which isn't as efficient as possible. The following code snippets demonstrate the three different ways to retrieve AUTO_INCREMENT values. First, we demonstrate the use of the new JDBC 3.0 method getGeneratedKeys() which is now the preferred method to use if you need to retrieve AUTO_INCREMENT keys and have access to JDBC 3.0. The second example shows how you can retrieve the same value using a standard SELECT 1528426 query. The final example shows how updatable result sets can retrieve the AUTO_INCREMENT value when using the insertRow() method.

Это означает, что до JDBC3.0 некоторые определения были перепутаны и не унифицированы, а затем объединены в метод getGeneratedKeys(). Обе стороны одинаковы. Принцип реализации в основном заключается в том, что сторона базы данных возвращаетLAST_INSERT_ID. это сauto_increment_idсильная корреляция.

ПосмотримОпределение auto_increment_id. Сосредоточьтесь на объемных вставках

For a multiple-row insert, 1528426 and mysql_insert_id() actually return the AUTO_INCREMENT key from the first of the inserted rows. This enables multiple-row inserts to be reproduced correctly on other servers in a replication setup.

При вставке партиями возвращается толькоидентификатор, это значение идентификатора является значением AUTO_INCREMENT первой вставленной строки. Что касается того, почему это сделано, то причину, по которой mysql-server может обеспечить единство значений id при архитектуре master-slave, можно увидеть ниже.это. Эта статья не будет расширяться.

Тогда сервер mysql возвращает только один идентификатор, как клиент может получить все идентификаторы при их вставке партиями?

3. Реализация клиента

давайте посмотрим на клиентаgetGeneratedKeysреализация.

JDBC com.mysql.jdbc.StatementImpl

public synchronized ResultSet getGeneratedKeys() throws SQLException {
       if (!this.retrieveGeneratedKeys) {
           throw SQLError.createSQLException(Messages.getString("Statement.GeneratedKeysNotRequested"), "S1009", this.getExceptionInterceptor());
       } else if (this.batchedGeneratedKeys == null) {
           // 批量走这边的逻辑
           return this.lastQueryIsOnDupKeyUpdate ? this.getGeneratedKeysInternal(1) : this.getGeneratedKeysInternal();
       } else {
           Field[] fields = new Field[]{new Field("", "GENERATED_KEY", -5, 17)};
           fields[0].setConnection(this.connection);
           return ResultSetImpl.getInstance(this.currentCatalog, fields, new RowDataStatic(this.batchedGeneratedKeys), this.connection, this, false);
       }
   }

Посмотрите на метод, называемыйthis.getGeneratedKeysInternal()

protected ResultSet getGeneratedKeysInternal() throws SQLException {
        // 获取影响的行数
        int numKeys = this.getUpdateCount();
        return this.getGeneratedKeysInternal(numKeys);
    }

Здесь есть важный момент: сначала получите количество затронутых строк в этой пакетной вставке, а затем выполните операцию получения конкретного идентификатора.

метод getGeneratedKeysInternal


protected synchronized ResultSet getGeneratedKeysInternal(int numKeys) throws SQLException {
       Field[] fields = new Field[]{new Field("", "GENERATED_KEY", -5, 17)};
       fields[0].setConnection(this.connection);
       fields[0].setUseOldNameMetadata(true);
       ArrayList rowSet = new ArrayList();
       long beginAt = this.getLastInsertID();
        // 按照受影响的范围+递增步长
        for(int i = 0; i < numKeys; ++i) {
              if (beginAt > 0L) {
                       // 值塞进去
                       row[0] = StringUtils.getBytes(Long.toString(beginAt));
                   }
            beginAt += (long)this.connection.getAutoIncrementIncrement();
        }
}

Перебрать количество затронутых строк, а затем получить идентификатор по очереди.

Таким образом, пакетная вставка верна и может быть возвращена. Однако есть проблема с пакетной вставкойOrUpdate.Количество строк, затронутых пакетной вставкойOrUpdate, не является количеством вставленных строк данных, а может быть 0, 1 или 2, что приводит к проблеме с самоувеличивающимся идентификатором.

Например, если вставить 3 элемента данных, 2 будут обновлены, а вставлен 1. В это время updateCount будет равен 5, а generateid будет равен 5. Затем Mybatis возьмет первые 3 и вставит их в данные, которые явно неверны.

Вышеизложенный принцип анализа.Если вы хотите узнать более подробные экспериментальные результаты, вы можете увидеть следующееэксперимент

Суммировать

пакетная вставка

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

Пример с официального сайта, в маппере параметр @Param указывать нельзя, иначе будут проблемы

Массовая вставка или обновление

Вы не можете полагаться на useGeneratedKey для возврата первичного ключа.

Обратите внимание на публичный аккаунт [Abbot's Temple], как можно скорее получите обновление статьи и начните путь технической практики вместе с аббатом

在这里插入图片描述

Ссылаться на

blog.CSDN.net/Монахи и люди/Арити…

blog.CSDN.net/QQ_27680317…

Dev.MySQL.com/doc/connect…

Dev.MySQL.com/doc/Furious/…

Dev.MySQL.com/doc/Furious/…