Тупик при удалении дочерних таблиц разделов

У нас есть база данных, содержащая статистические данные. Таблицы секционируются с использованием наследования по времени. Новые дочерние таблицы создаются по мере необходимости в соответствии с поступающими данными, и приложение выполняет ночное задание, удаляющее старые дочерние таблицы.

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

Мы обнаруживаем, что теперь возникают взаимоблокировки между последовательностью DROP и обычными запросами SELECT. Приведенный ниже (значительно упрощенный) пример иллюстрирует проблему:

  1. DDL и вставить фиктивную строку

    CREATE TABLE a(id serial primary key, t timestamp with time zone, i int);
    CREATE TABLE a1 () inherits (a);
    CREATE TABLE a2 () inherits (a);
    CREATE TABLE b(id serial primary key, id_a int, x int, y int);
    CREATE TABLE b1 () inherits (b);
    CREATE TABLE b2 () inherits (b);
    
    INSERT INTO a1(t,i) VALUES (CURRENT_TIMESTAMP, 100);
    INSERT INTO b1(id_a,x,y) VALUES (1,200,300);
    
  2. В одном сеансе psql выполните следующее (имитируя длительный запрос):

    SELECT pg_sleep(90) 
    FROM b
    LEFT JOIN a on a.id=b.id_a;
    
  3. Во втором сеансе psql выполните следующее:

    BEGIN;
    DROP TABLE a2;
    DROP TABLE b2;
    
  4. В третьем сеансе запустите это (тот же запрос, что и в первом сеансе):

    SELECT pg_sleep(90) 
    FROM b
    LEFT JOIN a on a.id=b.id_a;
    

Когда запрос, запущенный на шаге 2, завершается или прерывается, возникает взаимоблокировка.

DETAIL:  Process 19894 waits for AccessExclusiveLock on relation 129716 of database 44449; blocked by process 20017.
Process 20017 waits for AccessShareLock on relation 129700 of database 44449; blocked by process 19894.

Проблему легко обнаружить: блокировки, взятые транзакцией DROP, расположены в порядке a2, затем b2, но блокировки устанавливаются в обратном порядке операторами SELECT. Кажется, что операторы SELECT берут свои блокировки в порядке соединений в запросе.

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

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

Во-первых, мы обнаружили, что в зависимости от запроса SELECT, который мы пытаемся выполнить, мы получаем гораздо худшую производительность при использовании одного порядка соединения по сравнению с другим (я знаю, что этого не должно быть, но планировщик выбирает лучший план, когда мы используем один способ соединения). порядок по сравнению с другим).

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

Я прав, предполагая, что порядок соединения определяет порядок, в котором берутся блокировки для запроса SELECT?

Есть ли способ избежать этих взаимоблокировок без применения определенного порядка соединения для запросов SELECT только для чтения?

Мы используем Postgresql 9.6.6.


person harmic    schedule 15.01.2018    source источник


Ответы (1)


Порядок, в котором обрабатываются таблицы, не обязательно совпадает с порядком, в котором они отображаются в запросе. Но этот порядок соединения определяется во время планирования, а во время планирования таблицы сначала будут обрабатываться (и блокироваться) в том порядке, в котором они появляются в запросе.

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

person Laurenz Albe    schedule 15.01.2018
comment
Спасибо. Мы делаем запрос к родительским таблицам (таблицы a, b в приведенном выше примере). Пробовал LOCK TABLE a,b IN ACCESS EXCLUSIVE MODE; перед удалением любых таблиц, но не помогает, все равно получается тупик. - person harmic; 16.01.2018
comment
Блокируйте только одну родительскую таблицу на транзакцию, то есть используйте несколько транзакций для удаления разделов в нескольких таблицах. - person Laurenz Albe; 16.01.2018
comment
Как указано в вопросе, мы хотим сделать это за одну транзакцию, чтобы убедиться, что либо все связанные дочерние таблицы удалены, либо нет. Мы могли бы добавить больше кода уровня приложения для обеспечения целостности, но я надеялся, что в этом нет необходимости. - person harmic; 17.01.2018
comment
При таком требовании тупик кажется неизбежным. Возможно, вы могли бы приостановить всю параллельную деятельность на это время. - person Laurenz Albe; 17.01.2018