PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Как да направя големи неблокиращи актуализации в PostgreSQL?

Колона/ред

... Нямам нужда да се поддържа целостта на транзакциите през цялата операция, защото знам, че колоната, която променям, няма да бъде записана или прочетена по време на актуализацията.

Всякакви UPDATE в модела MVCC на PostgreSQL пише нова версия на целия ред . Ако едновременните транзакции променят някои колона на същия ред, възникват отнемащи време проблеми с едновременността. Подробности в ръководството. Познавайки същата колона няма да бъде засегнат от едновременни транзакции избягва някои възможни усложнения, но не и други.

Индекс

За да избегнем пренасочване към дискусия извън темата, нека приемем, че всички стойности на състоянието за 35 милиона колони в момента са настроени на една и съща (не-нула) стойност, което прави индекса безполезен.

При актуализиране нацялата таблица (или големи части от него) Postgres никога не използва индекс . Последователното сканиране е по-бързо, когато трябва да се прочетат всички или повечето редове. Напротив:Поддръжката на индекса означава допълнителни разходи за UPDATE .

Ефективност

Например, да кажем, че имам таблица, наречена "поръчки" с 35 милиона реда, и искам да направя това:

UPDATE orders SET status = null;

Разбирам, че се стремите към по-общо решение (вижте по-долу). Но за да отговорим на същинския въпрос попита:Това може да бъде решено за един милисекунди , независимо от размера на таблицата:

ALTER TABLE orders DROP column status
                 , ADD  column status text;

Ръководството (до Postgres 10):

Когато колона се добави с ADD COLUMN , всички съществуващи редове в таблицата се инициализират със стойността по подразбиране на колоната (NULL ако няма DEFAULT е посочена клауза). Ако няма DEFAULT клауза, това е просто промяна на метаданните [...]

Ръководството (от Postgres 11):

Когато колона се добави с ADD COLUMN и енергонезависим DEFAULT е посочено, по подразбиране се оценява в момента на израза и резултатът се съхранява в метаданните на таблицата. Тази стойност ще се използва за колоната за всички съществуващи редове. Ако не DEFAULT е посочено, използва се NULL. И в двата случая не се изисква пренаписване на таблицата.

Добавяне на колона с променлив DEFAULT или промяната на типа на съществуващата колона ще изисква записване на цялата таблица и нейните индекси. [...]

И:

DROP COLUMN form не премахва физически колоната, а просто я прави невидима за SQL операциите. Последващите операции за вмъкване и актуализиране в таблицата ще съхранят нулева стойност за колоната. По този начин пускането на колона е бързо, но няма да намали незабавно размера на диска на вашата таблица, тъй като пространството, заето от изпуснатата колона, не се възстановява. Мястото ще бъде възстановено с течение на времето, тъй като съществуващите редове се актуализират.

Уверете се, че нямате обекти в зависимост от колоната (ограничения на външния ключ, индекси, изгледи, ...). Ще трябва да ги пуснете/пресъздадете. Като изключим това, малки операции върху таблицата на системния каталог pg_attribute свърши работата. Изисква изключително заключване на масата, което може да е проблем при тежко едновременно натоварване. (Както Buurman подчертава в коментара си.) Само на това, операцията е въпрос на милисекунди.

Ако имате колона по подразбиране, която искате да запазите, добавете я обратно в отделна команда . Правейки го в една и съща команда, я прилага незабавно към всички редове. Вижте:

  • Да се ​​добави ли нова колона без заключване на таблицата?

За да приложите реално по подразбиране, помислете дали да го правите на партиди:

  • PostgreSQL оптимизира ли добавянето на колони с стойности по подразбиране, които не са NULL?

Общо решение

dblink е споменато в друг отговор. Той позволява достъп до "отдалечени" Postgres бази данни в имплицитни отделни връзки. „Отдалечената“ база данни може да бъде текущата, като по този начин се постигат „автономни транзакции“ :това, което функцията записва в "отдалечената" база данни, е ангажирано и не може да бъде върнато.

Това позволява да се изпълнява една функция, която обновява голяма таблица на по-малки части и всяка част се ангажира отделно. Избягва натрупването на транзакционни разходи за много голям брой редове и, което е по-важно, освобождава заключванията след всяка част. Това позволява на едновременните операции да продължат без много забавяне и намалява вероятността от блокиране.

Ако нямате едновременен достъп, това едва ли е полезно - освен за избягване на ROLLBACK след изключение. Също така помислете за SAVEPOINT за този случай.

Отказ от отговорност

На първо място, много малки транзакции всъщност са по-скъпи. Товаи има смисъл само за големи маси . Сладкото място зависи от много фактори.

Ако не сте сигурни какво правите:една транзакция е безопасният метод . За да работи правилно, едновременните операции на масата трябва да играят заедно. Например:едновременни записвания може да премести ред към дял, за който се предполага, че вече е обработен. Или едновременното четене може да види непоследователни междинни състояния. Бяхте предупредени.

Инструкции стъпка по стъпка

Първо трябва да се инсталира допълнителният модул dblink:

  • Как да използвам (инсталирам) dblink в PostgreSQL?

Настройката на връзката с dblink много зависи от настройката на вашия DB клъстер и политиките за сигурност. Може да е трудно. Свързан по-късен отговор с повече как да се свържете с dblink :

  • Постоянни вмъквания в UDF, дори ако функцията се прекрати

Създайте FOREIGN SERVER и USER MAPPING както е указано там за опростяване и рационализиране на връзката (освен ако вече нямате такава).
Приемане на serial PRIMARY KEY със или без пропуски.

CREATE OR REPLACE FUNCTION f_update_in_steps()
  RETURNS void AS
$func$
DECLARE
   _step int;   -- size of step
   _cur  int;   -- current ID (starting with minimum)
   _max  int;   -- maximum ID
BEGIN
   SELECT INTO _cur, _max  min(order_id), max(order_id) FROM orders;
                                        -- 100 slices (steps) hard coded
   _step := ((_max - _cur) / 100) + 1;  -- rounded, possibly a bit too small
                                        -- +1 to avoid endless loop for 0
   PERFORM dblink_connect('myserver');  -- your foreign server as instructed above

   FOR i IN 0..200 LOOP                 -- 200 >> 100 to make sure we exceed _max
      PERFORM dblink_exec(
       $$UPDATE public.orders
         SET    status = 'foo'
         WHERE  order_id >= $$ || _cur || $$
         AND    order_id <  $$ || _cur + _step || $$
         AND    status IS DISTINCT FROM 'foo'$$);  -- avoid empty update

      _cur := _cur + _step;

      EXIT WHEN _cur > _max;            -- stop when done (never loop till 200)
   END LOOP;

   PERFORM dblink_disconnect();
END
$func$  LANGUAGE plpgsql;

Обадете се:

SELECT f_update_in_steps();

Можете да параметризирате всяка част според вашите нужди:името на таблицата, името на колоната, стойността, ... просто не забравяйте да дезинфекцирате идентификаторите, за да избегнете SQL инжекция:

  • Име на таблица като параметър на функцията на PostgreSQL

Избягвайте празни АКТУАЛИЗАЦИИ:

  • Как (или мога) да ИЗБЕРЕМ DISTINCT на няколко колони?


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Създайте PostgreSQL база данни в движение, като използвате Hibernate, дори ако DB не съществува

  2. Intel SSD, вече извън списъка на sh..err, sramed list

  3. Грешка при създаването на пространствена база данни. ГРЕШКА:не можа да се зареди библиотеката /usr/pgsql-9.1/lib/rtpostgis-2.0.so

  4. Какъв е правилният начин за използване на модула node.js postgresql?

  5. Не може да се свърже PostgreSQL към отдалечена база данни чрез pgAdmin