Обмен технологиями

Как обрабатывать конфликты при одновременном обновлении данных в PostgreSQL?

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

красивая разделительная линия

PostgreSQL


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

красивая разделительная линия

1. Сценарии конфликта одновременных обновлений

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

  1. Одновременное изменение разных столбцов одной и той же строки
  2. Одновременно обновлять разные значения одного и того же столбца

красивая разделительная линия

2. Механизм управления параллелизмом в PostgreSQL

PostgreSQL в основном использует MVCC (управление многоверсионным параллелизмом) для обработки параллельных транзакций. MVCC позволяет транзакции читать версию данных, которая соответствует требованиям уровня изоляции, без блокировки операций чтения других транзакций. Однако во время операций записи по-прежнему могут возникать конфликты.

(1) Механизм блокировки

PostgreSQL использует несколько типов блокировок для управления одновременным доступом к данным. К распространенным типам замков относятся:

  1. Общая блокировка: позволяет другим транзакциям получать общие блокировки, но предотвращает получение эксклюзивных блокировок. Обычно используется для операций чтения.
  2. Эксклюзивная блокировка: предотвращает получение другими транзакциями любого типа блокировки, часто используемой для операций записи.

Степень детализации блокировки может быть на уровне строки (Row-Level), на уровне страницы (Page-Level) и на уровне таблицы (Table-Level).

(2) Уровень изоляции транзакции

PostgreSQL поддерживает четыре уровня изоляции транзакций:

  1. Читать незафиксированные: это самый низкий уровень изоляции. Транзакция может считывать незафиксированные изменения данных других транзакций, что может привести к таким проблемам, как грязное чтение, неповторяемое чтение и фантомное чтение.
  2. Чтение зафиксировано: Транзакции могут только читать отправленные данные, избегая грязного чтения, но неповторяемые чтения и фантомные чтения все равно могут происходить.
  3. Повторяемое чтение: чтение одних и тех же данных несколько раз в рамках транзакции приведет к одному и тому же результату, что позволяет избежать неповторяющихся операций чтения, однако может произойти фантомное чтение.
  4. Сериализуемый: самый высокий уровень изоляции, обеспечивающий последовательное выполнение транзакций посредством строгого контроля параллелизма, позволяющий избежать грязного чтения, неповторяемого чтения и фантомного чтения.

красивая разделительная линия

3. Решение конфликтов одновременных обновлений.

(1) Механизм повторной попытки

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

DO
$$
DECLARE
    conflict_detected BOOLEAN := FALSE;
BEGIN
    LOOP
        -- 尝试执行更新操作
        UPDATE products SET price = 100 WHERE id = 1;

        -- 检查是否有冲突(例如,通过检查受影响的行数)
        IF NOT FOUND THEN
            conflict_detected := TRUE;
        ELSE
            EXIT;
        END IF;

        -- 若有冲突,等待一段时间并重试
        IF conflict_detected THEN
            PERFORM pg_sleep(1);
        END IF;
    END LOOP;
END;
$$;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

В приведенном выше примере, если операция обновления не затрагивает ни одну строку (что указывает на возможный конфликт), устанавливается флаг, происходит некоторое время ожидания, а затем повторяется попытка.

(2) Используйте оптимистичный контроль параллелизма.

Оптимистическое управление параллелизмом предполагает, что конфликты параллелизма возникают редко. В этом методе транзакция не блокируется при обновлении данных, а проверяет, были ли данные изменены другими транзакциями при фиксации. Если конфликтов нет, транзакция фиксируется успешно; если конфликты есть, транзакция откатывается и повторяется при необходимости.

-- 获取数据的初始版本
SELECT price AS original_price FROM products WHERE id = 1;

-- 进行业务处理和修改
UPDATE products SET price = 100 WHERE id = 1 AND price = original_price;
  • 1
  • 2
  • 3
  • 4
  • 5

В приведенном выше примере операция обновления завершается успешно, только если данные не были изменены другими транзакциями.

(3) Используйте пессимистическое управление параллелизмом.

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

BEGIN;

-- 获取排他锁
LOCK TABLE products IN SHARE ROW EXCLUSIVE MODE;

-- 进行数据更新
UPDATE products SET price = 100 WHERE id = 1;

COMMIT;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

(4) Поле версии приложения.

Добавьте в таблицу поле версии, чтобы отслеживать изменения данных.

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    price DECIMAL(10, 2),
    version INT DEFAULT 0
);
  • 1
  • 2
  • 3
  • 4
  • 5

При обновлении данных также увеличьте поле версии:

UPDATE products SET price = 100, version = version + 1 WHERE id = 1 AND version = <expected_version>;
  • 1

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

(5) Разрешение конфликтов на основе временной метки

Добавьте поле метки времени в каждую строку данных, чтобы записать время последнего изменения данных.

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    price DECIMAL(10, 2),
    last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  • 1
  • 2
  • 3
  • 4
  • 5

При обновлении обновляйте данные только с отметкой времени, более старой, чем отметка времени, считанная текущей транзакцией:

UPDATE products SET price = 100 WHERE id = 1 AND last_modified <= <read_timestamp>;
  • 1

красивая разделительная линия

4. Рекомендации по практическому применению

(1) Влияние на производительность

  1. Различные методы разрешения конфликтов по-разному влияют на производительность базы данных. Например, использование блокировки может привести к ожиданию других транзакций, увеличению времени блокировки системы и, таким образом, повлиять на параллелизм. Оптимистическое управление параллелизмом работает лучше, когда конфликты возникают редко, но когда конфликты происходят часто, это может привести к большому количеству повторных попыток транзакций, увеличивая общее время выполнения.
  2. Применение полей версии или методов на основе меток времени может потребовать дополнительного места для хранения информации о версии или метке времени, а также дополнительных оценок и обработки при обновлении.

(2) Адаптивность бизнес-логики

  1. Определенные бизнес-сценарии могут быть более подходящими для конкретного метода разрешения конфликтов. Например, если в бизнесе очень высокие требования к согласованности данных и он не терпит каких-либо несоответствий, тогда лучшим выбором может быть пессимистический контроль параллелизма или уровень изоляции сериализации.
  2. Оптимистическое управление параллелизмом может быть более подходящим для сценариев, где конфликты менее часты, а требования ко времени ответа выше.

(3) Модели распределения и доступа к данным

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

красивая разделительная линия

5. Пример анализа

Предположим, у нас есть система управления запасами для интернет-магазина с inventory Таблица для хранения инвентарных количеств предметов.

CREATE TABLE inventory (
    product_id INT PRIMARY KEY,
    quantity INT,
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  • 1
  • 2
  • 3
  • 4
  • 5

Теперь есть две одновременные транзакции:

Транзакция 1:

BEGIN;
SELECT * FROM inventory WHERE product_id = 1;
-- 假设读取到的数量为 10
UPDATE inventory SET quantity = 5 WHERE product_id = 1 AND last_updated <= <read_timestamp>;
COMMIT;
  • 1
  • 2
  • 3
  • 4
  • 5

Транзакция 2:

BEGIN;
SELECT * FROM inventory WHERE product_id = 1;
-- 假设也读取到的数量为 10
UPDATE inventory SET quantity = 8 WHERE product_id = 1 AND last_updated <= <read_timestamp>;
COMMIT;
  • 1
  • 2
  • 3
  • 4
  • 5

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

Если мы используем разрешение конфликтов на основе временных меток:

  1. Транзакция 1 получила текущую метку времени при чтении данных (T1)。
  2. Транзакция 2 получила немного более позднюю временную метку при чтении данных (T2)。

Когда транзакция 1 пытается обновить данные, если ни одна другая транзакция не изменила данные с момента их чтения (т. last_updated <= T1), обновление прошло успешно.

Когда транзакция 2 пытается обновиться, если она обнаруживает, что данные last_updated больше, чемT2(Указывая, что он был изменен после того, как транзакция 2 прочитала его), обновление завершается неудачей, и транзакция 2 может выбрать откат и попытку еще раз или выполнить другую обработку в соответствии с бизнес-логикой.


красивая разделительная линия

🎉相关推荐

PostgreSQL