Колона/ред
... Нямам нужда да се поддържа целостта на транзакциите през цялата операция, защото знам, че колоната, която променям, няма да бъде записана или прочетена по време на актуализацията.
Всякакви 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 на няколко колони?