Կան իրավիճակներ, երբ սեղան առանց հիմնական բանալիի կամ որևէ այլ եզակի ինդեքս, հսկողության պատճառով, ներառված են արդեն գոյություն ունեցող գրառումների ամբողջական կլոններ:
Օրինակ, ժամանակագրական չափման արժեքները գրվում են PostgreSQL-ում՝ օգտագործելով COPY հոսքը, այնուհետև հանկարծակի ձախողում է տեղի ունենում, և լրիվ նույնական տվյալների մի մասը նորից գալիս է:
Ինչպե՞ս ազատել տվյալների բազան ավելորդ կլոններից:
Երբ PK-ն օգնական չէ
Ամենահեշտ ճանապարհն առաջին հերթին նման իրավիճակի առաջացումը կանխելն է: Օրինակ՝ գլորել PRIMARY KEY: Բայց դա միշտ չէ, որ հնարավոր է առանց պահեստավորված տվյալների ծավալի մեծացման:
Օրինակ, եթե աղբյուրի համակարգի ճշգրտությունը ավելի բարձր է, քան դաշտի ճշգրտությունը տվյալների բազայում.
metric | ts | data
--------------------------------------------------
cpu.busy | 2019-12-20 00:00:00 | {"value" : 12.34}
cpu.busy | 2019-12-20 00:00:01 | {"value" : 10}
cpu.busy | 2019-12-20 00:00:01 | {"value" : 11.2}
cpu.busy | 2019-12-20 00:00:03 | {"value" : 15.7}
Նկատեցի՞ք։ 00:00:02-ի փոխարեն հետհաշվարկը գրանցվել է տվյալների բազայում ts-ով մեկ վայրկյան առաջ, բայց կիրառական տեսանկյունից բավականին վավեր է մնացել (ի վերջո, տվյալների արժեքները տարբեր են):
Իհարկե, դուք կարող եք դա անել PK (մետրիկ, ts) - բայց այնուհետև մենք կստանանք ներդիրների հակասություններ վավեր տվյալների համար:
Կարող է անել PK (մետրիկ, ts, տվյալներ) - բայց դա մեծապես կբարձրացնի դրա ծավալը, որը մենք չենք օգտագործի:
Հետեւաբար, ամենաճիշտ տարբերակը սովորական ոչ եզակի ինդեքս պատրաստելն է (մետրիկ, ts) և խնդիրներով զբաղվել այն բանից հետո, եթե դրանք առաջանան:
«Կլոնիկ պատերազմը սկսված է».
Ինչ-որ դժբախտ պատահար է տեղի ունեցել, և այժմ մենք պետք է ոչնչացնենք կլոնի գրառումները աղյուսակից:
Եկեք մոդելավորենք սկզբնական տվյալները.
CREATE TABLE tbl(k text, v integer);
INSERT INTO tbl
VALUES
('a', 1)
, ('a', 3)
, ('b', 2)
, ('b', 2) -- oops!
, ('c', 3)
, ('c', 3) -- oops!!
, ('c', 3) -- oops!!
, ('d', 4)
, ('e', 5)
;
Այստեղ մեր ձեռքը երեք անգամ դողաց, Ctrl+V-ն խրվեց, իսկ հիմա...
Նախ, եկեք հասկանանք, որ մեր սեղանը կարող է շատ մեծ լինել, ուստի բոլոր կլոնները գտնելուց հետո մեզ խորհուրդ է տրվում բառացիորեն «մատը խոթել» ջնջելու համար: կոնկրետ գրառումներ՝ առանց դրանք նորից փնտրելու.
Եվ կա այդպիսի միջոց՝ սա
Այսինքն, առաջին հերթին մենք պետք է հավաքենք գրառումների ctid-ը աղյուսակի տողի ամբողջական բովանդակության համատեքստում։ Ամենապարզ տարբերակն է ամբողջ տողը տեքստի մեջ գցել.
SELECT
T::text
, array_agg(ctid) ctids
FROM
tbl T
GROUP BY
1;
t | ctids
---------------------------------
(e,5) | {"(0,9)"}
(d,4) | {"(0,8)"}
(c,3) | {"(0,5)","(0,6)","(0,7)"}
(b,2) | {"(0,3)","(0,4)"}
(a,3) | {"(0,2)"}
(a,1) | {"(0,1)"}
Հնարավո՞ր է չնկարահանել:Սկզբունքորեն դա հնարավոր է շատ դեպքերում։ Քանի դեռ չեք սկսել օգտագործել այս աղյուսակի դաշտերը տեսակներ առանց հավասարության օպերատորի:
CREATE TABLE tbl(k text, v integer, x point);
SELECT
array_agg(ctid) ctids
FROM
tbl T
GROUP BY
T;
-- ERROR: could not identify an equality operator for type tbl
Այո, մենք անմիջապես տեսնում ենք, որ եթե զանգվածում մեկից ավելի մուտք կա, սրանք բոլորը կլոններ են: Եկեք պարզապես թողնենք դրանք.
SELECT
unnest(ctids[2:])
FROM
(
SELECT
array_agg(ctid) ctids
FROM
tbl T
GROUP BY
T::text
) T;
unnest
------
(0,6)
(0,7)
(0,4)
Նրանց համար, ովքեր սիրում են ավելի կարճ գրելԿարող եք նաև գրել այսպես.
SELECT
unnest((array_agg(ctid))[2:])
FROM
tbl T
GROUP BY
T::text;
Քանի որ սերիականացված տողի արժեքը ինքնին մեզ համար հետաքրքիր չէ, մենք այն պարզապես դուրս նետեցինք ենթհարցման վերադարձված սյունակներից:
Քիչ բան է մնացել անելու. ստիպեք ՋՆՋԵԼ օգտագործել մեր ստացած հավաքածուն.
DELETE FROM
tbl
WHERE
ctid = ANY(ARRAY(
SELECT
unnest(ctids[2:])
FROM
(
SELECT
array_agg(ctid) ctids
FROM
tbl T
GROUP BY
T::text
) T
)::tid[]);
Եկեք ստուգենք ինքներս մեզ.
Այո, ամեն ինչ ճիշտ է. մեր 3 գրառումներն ընտրվել են ամբողջ աղյուսակի մեկ Seq Scan-ի համար, և Ջնջել հանգույցն օգտագործվել է տվյալների որոնման համար: միայնակ փոխանցում Tid Scan-ով:
-> Tid Scan on tbl (actual time=0.050..0.051 rows=3 loops=1)
TID Cond: (ctid = ANY ($0))
Եթե դուք մաքրել եք շատ գրառումներ,
Եկեք ստուգենք ավելի մեծ աղյուսակ և ավելի մեծ թվով կրկնօրինակներով.
TRUNCATE TABLE tbl;
INSERT INTO tbl
SELECT
chr(ascii('a'::text) + (random() * 26)::integer) k -- a..z
, (random() * 100)::integer v -- 0..99
FROM
generate_series(1, 10000) i;
Այսպիսով, մեթոդը հաջողությամբ աշխատում է, բայց այն պետք է օգտագործվի որոշակի զգուշությամբ: Որովհետև ջնջված յուրաքանչյուր գրառման համար կա մեկ տվյալների էջ, որը կարդացվում է Tid Scan-ում և մեկը՝ Delete-ում:
Source: www.habr.com