PostgreSQL এর সাথে প্রতি সারিতে একটি অনন্য কাউন্টার কীভাবে রাখবেন?


10

আমাকে একটি ডকুমেন্ট_সিরীক্ষণ সারণীতে একটি অনন্য (প্রতি-সারি) সংশোধন নম্বর রাখতে হবে, যেখানে সংশোধন নম্বরটি কোনও নথিতে স্কোপ করা আছে, সুতরাং এটি কেবলমাত্র সম্পর্কিত নথিতেই পুরো টেবিলের কাছে অনন্য নয়।

আমি প্রথমদিকে এমন কিছু নিয়ে এসেছি:

current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);

তবে রেসের শর্ত আছে!

আমি এটি দিয়ে সমাধান করার চেষ্টা করছি pg_advisory_lock, তবে ডকুমেন্টেশনটি কিছুটা দুষ্প্রাপ্য এবং আমি এটি পুরোপুরি বুঝতে পারি না এবং আমি ভুল করে কিছু লক করতে চাই না।

নিম্নলিখিতটি গ্রহণযোগ্য, বা আমি এটি ভুল করছি, বা এর থেকে আরও ভাল সমাধান আছে কি?

SELECT pg_advisory_lock(123);
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(123);

পরিবর্তে প্রদত্ত ক্রিয়াকলাপ (কী 2) এর জন্য আমি কী দস্তাবেজ সারি (কী 1) লক করব না? সুতরাং এটি সঠিক সমাধান হবে:

SELECT pg_advisory_lock(id, 1) FROM documents WHERE id = 123;
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(id, 1) FROM documents WHERE id = 123;

হতে পারে আমি পোস্টগ্রাইএসকিউএল এর সাথে অভ্যস্ত নই এবং একটি সিরিয়াল বাছাই করা যেতে পারে, বা একটি ক্রম nextval()হতে পারে এবং কাজটি আরও ভাল করতে পারে?


"প্রদত্ত ক্রিয়াকলাপের জন্য" আপনি কী বোঝাতে চেয়েছিলেন এবং "কী 2" কোথা থেকে এসেছে তা আমি বুঝতে পারি না।
ট্রাইগভে লগস্টেল

2
আপনার লক করার কৌশলটি ঠিক মনে হচ্ছে যদি আপনি হতাশবাদী লকিং চান তবে আমি pg_advisory_xact_lock ব্যবহার করব যাতে সমস্ত লক স্বয়ংক্রিয়ভাবে COMMIT / ROLLBACK এ প্রকাশিত হয়।
ট্রিগভে লগস্টেল

উত্তর:


2

ধরে নেওয়া যাক আপনি একটি সারণিতে নথির সব পুনর্বিবেচনা সঞ্চয় একটি পন্থা হবে না পুনর্বিবেচনা সংখ্যা সংরক্ষণ কিন্তু টেবিল সঞ্চিত পুনর্বিবেচনা সংখ্যার উপর ভিত্তি করে এটা নিরূপণ।

এটি মূলত একটি উদ্ভূত মান, এমন কিছু নয় যা আপনার সঞ্চয় করতে হবে।

একটি উইন্ডো ফাংশন রিভিশন নম্বর গণনা করতে ব্যবহার করা যেতে পারে, এরকম কিছু

row_number() over (partition by document_id order by <change_date>)

এবং আপনার একটি কলামের কিছু দরকার change_dateযা সংশোধনগুলির ক্রম ট্র্যাক করে রাখুন need


অন্যদিকে, যদি আপনার কাছে কেবল revisionনথির সম্পত্তি হিসাবে থাকে এবং এটি "ডকুমেন্টটি কতবার পরিবর্তিত হয়েছে" নির্দেশ করে, তবে আমি আশাবাদী লকিং পদ্ধতির পক্ষে যাব, এরকম কিছু:

update documents
set revision = revision + 1
where document_id = <id> and revision = <old_revision>;

যদি এটি 0 টি সারি আপডেট করে, তবে অন্তর্বর্তী আপডেট হয়েছে এবং আপনাকে এটির ব্যবহারকারীকে অবহিত করতে হবে।


সাধারণভাবে, আপনার সমাধানটি যতটা সম্ভব সহজ করার চেষ্টা করুন। এই ক্ষেত্রে

  • সম্পূর্ণ প্রয়োজন না হলে সুস্পষ্ট লকিংয়ের ব্যবহারগুলি এড়ানো
  • কম ডাটাবেস অবজেক্ট (প্রতিটি নথির ক্রম নেই) এবং কম অ্যাট্রিবিউটস সংরক্ষণ করা (এটি যদি গণনা করা যায় তবে পুনর্বিবেচনাটি সংরক্ষণ করবেন না)
  • একটি বা updateএকটি selectঅনুসরণ দ্বারা পরিবর্তে একটি একক বিবৃতি ব্যবহারinsertupdate

প্রকৃতপক্ষে, মানটি যখন গণনা করা যায় তখন আমার সঞ্চয় করার দরকার নেই। আমাকে মনে করার জন্য ধন্যবাদ!
জুলিয়ান পোর্টালিয়ার

2
প্রকৃতপক্ষে, আমার প্রসঙ্গে, পুরানো সংশোধনগুলি কোনও এক সময়ে মুছে ফেলা হবে, তাই আমি এটি গণনা করতে পারব না বা পুনর্বিবেচনার সংখ্যা হ্রাস পাবে :)
জুলিয়েন পোর্টালিয়ার

3

সিকোয়েন্সটি অনন্য হওয়ার গ্যারান্টিযুক্ত এবং আপনার নথির সংখ্যা খুব বেশি না হলে আপনার ব্যবহারের ক্ষেত্রে প্রযোজ্য মনে হচ্ছে (অন্যথায় আপনাকে পরিচালনা করার জন্য অনেকগুলি সিকোয়েন্স রয়েছে)। ক্রম দ্বারা উত্পাদিত মান পেতে রিটার্নিং ক্লজটি ব্যবহার করুন। উদাহরণস্বরূপ, 'A36' নথি_আইডি হিসাবে ব্যবহার করা:

  • প্রতি নথিতে, আপনি ইনক্রিমেন্ট ট্র্যাক করার জন্য একটি সিকোয়েন্স তৈরি করতে পারেন।
  • সিকোয়েন্সগুলি পরিচালনা করতে কিছু যত্ন সহকারে পরিচালনা করা দরকার। সারণী document_idসন্নিবেশ / আপডেট করার সময় আপনি সম্ভবত নথির নাম এবং সেই সাথে যুক্ত ক্রম যুক্ত আলাদা আলাদা টেবিল রাখতে পারেন document_revisions

     CREATE SEQUENCE d_r_document_a36_seq;
    
     INSERT INTO document_revisions (document_id, rev)
     VALUES ('A36',nextval('d_r_document_a36_seq')) RETURNING rev;

দেশসো ফর্ম্যাটিংয়ের জন্য ধন্যবাদ, আমি আমার মন্তব্যে পেস্ট করার সময় এটি দেখতে কেমন খারাপ লাগছিল তা আমি খেয়াল করিনি।
বিএম

আপনি যদি পরবর্তী মানটি পূর্ববর্তী +1 করতে চান তবে সিকোয়েন্সটি একটি খারাপ কাউন্টার। কারণ এটি লেনদেনের মধ্যে না চলে।
ট্রিগভে লগস্টেল

1
অঁ্যা? সিকোয়েন্সগুলি পারমাণবিক। এজন্য আমি প্রতি নথিতে একটি সিকোয়েন্সের পরামর্শ দিয়েছি । এগুলিকে ফাঁক-মুক্ত থাকার গ্যারান্টিও দেওয়া হয় না, কারণ রোলব্যাকগুলি ক্রমবর্ধমান হওয়ার পরে ক্রমটি ডি-ইনক্রিমেন্ট করে না। আমি বলছি না যে সঠিক লক করা ভাল সমাধান নয়, কেবল সেই ক্রমগুলি একটি বিকল্প উপস্থাপন করে।
বিএম

1
ধন্যবাদ! আমার যদি পুনর্বিবেচনা নম্বরটি সঞ্চয় করতে হয় তবে সিকোয়েন্সগুলি অবশ্যই যাওয়ার উপায় are
জুলিয়ান পোর্টালিয়ার

2
নোট করুন যে বিপুল পরিমাণে সিকোয়েন্স থাকা কর্মক্ষমতাতে একটি বড় হিট, যেহেতু ক্রমটি মূলত একটি সারি সহ একটি টেবিল। আপনি যে সম্বন্ধে আরও পড়তে পারেন এখানে
Magnuss

2

এটি প্রায়শই আশাবাদী লক সহ সমাধান করা হয়:

SELECT version, x FROM foo;

version | foo
    123 | ..

UPDATE foo SET x=?, version=124 WHERE version=123

যদি আপডেটটি 0 টি সারি আপডেট করে, আপনি নিজের আপডেটটি মিস করেছেন কারণ অন্য কেউ ইতিমধ্যে সারিটি আপডেট করে।


ধন্যবাদ! আপনার যখন কোনও নথিতে আপডেটের পাল্টা রাখা দরকার এটি ভাল! তবে ডকুমেন্ট_সরিভিশন সারণীতে প্রতিটি সারিগুলির জন্য আমার একটি অনন্য সংশোধন সংখ্যা দরকার, যা আপডেট হবে না এবং অবশ্যই পূর্ববর্তী সংশোধনের অনুসারী হতে হবে (যেমন, পূর্ববর্তী সারি +1 এর সংশোধন সংখ্যা)।
জুলিয়ান পোর্টালিয়ার

1
এইচ, আপনি কেন এই কৌশলটি ব্যবহার করতে পারবেন না? এটি হ'ল একমাত্র পদ্ধতি (হতাশাবাদী লকিং ব্যতীত) যা আপনাকে ফাঁক-কম ক্রম দেবে।
ট্রাইগভে লগস্টেল

2

(এই বিষয় সম্পর্কে একটি নিবন্ধটি পুনরায় আবিষ্কার করার চেষ্টা করার সময় আমি এই প্রশ্নে এসেছি Now এখন আমি এটি খুঁজে পেয়েছি, অন্যরা বর্তমানে নির্বাচিত উত্তরের বিকল্প বিকল্পের সন্ধানের ক্ষেত্রে এটি এখানে পোস্ট করছি with row_number())

আমার এই একই ব্যবহারের মামলা আছে। আমাদের সাএসে একটি নির্দিষ্ট প্রকল্পের মধ্যে প্রবেশ করা প্রতিটি রেকর্ডের জন্য আমাদের একটি অনন্য, বর্ধনশীল সংখ্যা প্রয়োজন যা সমবর্তীগুলির মুখে তৈরি করা যেতে পারে INSERTএবং আদর্শভাবে ফাঁকবিহীন।

এই নিবন্ধটি একটি দুর্দান্ত সমাধান বর্ণনা করেছে , যা আমি এখানে স্বাচ্ছন্দ্য এবং উত্তরোত্তর জন্য সংক্ষিপ্ত করব।

  1. একটি পৃথক সারণী রয়েছে যা পরবর্তী মান সরবরাহ করতে কাউন্টার হিসাবে কাজ করে। এটিতে দুটি কলাম থাকবে, document_idএবং counter। বিকল্প হিসাবে counterহবে DEFAULT 0, আপনার যদি ইতিমধ্যে কোনও documentসত্ত্বা থাকে যা সমস্ত সংস্করণকে গোষ্ঠীভূত করে, একটি counterসেখানে যুক্ত করা যেতে পারে।
  2. সারণীতে একটি BEFORE INSERTট্রিগার যুক্ত করুন document_versionsযা পরমাণুভাবে কাউন্টারকে বাড়িয়ে তোলে ( UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter) এবং তারপরে NEW.versionসেই কাউন্টারটির মান সেট করে।

বিকল্পভাবে, আপনি অ্যাপ্লিকেশন স্তরে এটি করতে একটি সিটিই ব্যবহার করতে সক্ষম হতে পারেন (যদিও আমি এটি ধারাবাহিকতার জন্য ট্রিগার হতে পছন্দ করি):

WITH version AS (
  UPDATE document_revision_counters
    SET counter = counter + 1 
    WHERE document_id = 1
    RETURNING counter
)

INSERT 
  INTO document_revisions (document_id, rev, other_data)
  SELECT 1, version.counter, 'some other data'
  FROM "version";

এটি নীতিগতভাবে অনুরূপ যে আপনি কীভাবে এটি প্রাথমিকভাবে সমাধানের চেষ্টা করছেন, একক বিবৃতিতে পাল্টা সারিটি সংশোধন করে INSERTএটি প্রতিশ্রুতিবদ্ধ না হওয়া পর্যন্ত বাসি মানটি পড়তে অবরুদ্ধ করে ।

এটি কার্যকরভাবে psqlদেখানো থেকে একটি প্রতিলিপি এখানে :

scratch=# CREATE TABLE document_revisions (document_id integer, rev integer, other_data text, PRIMARY KEY (document_id, rev));
CREATE TABLE

scratch=# CREATE TABLE document_revision_counters (document_id integer PRIMARY KEY, counter integer DEFAULT 0);
CREATE TABLE

scratch=# WITH version AS (
    INSERT INTO document_revision_counters (document_id) VALUES (2)
      ON CONFLICT (document_id)
      DO UPDATE SET counter = document_revision_counters.counter + 1
      RETURNING counter;
  )
  INSERT 
    INTO document_revisions (document_id, rev, other_data)
    SELECT 2, version.counter, 'doc 1 v1'
    FROM "version";
INSERT 0 1

scratch=# WITH version AS (
    INSERT INTO document_revision_counters (document_id) VALUES (2)
      ON CONFLICT (document_id)
      DO UPDATE SET counter = document_revision_counters.counter + 1
      RETURNING counter;
  )
  INSERT 
    INTO document_revisions (document_id, rev, other_data)
    SELECT 2, version.counter, 'doc 1 v2'
    FROM "version";
INSERT 0 1

scratch=# WITH version AS (
    INSERT INTO document_revision_counters (document_id) VALUES (2)
      ON CONFLICT (document_id)
      DO UPDATE SET counter = document_revision_counters.counter + 1
      RETURNING counter;
  )
  INSERT 
    INTO document_revisions (document_id, rev, other_data)
    SELECT 2, version.counter, 'doc 2 v1'
    FROM "version";
INSERT 0 1

scratch=# SELECT * FROM document_revisions;
 document_id | rev | other_data 
-------------+-----+------------
           2 |   1 | doc 1 v1
           2 |   2 | doc 1 v2
           2 |   1 | doc 2 v1
(3 rows)

আপনি দেখতে পাচ্ছেন, কীভাবে এটি INSERTঘটে সে সম্পর্কে আপনাকে সতর্কতা অবলম্বন করতে হবে , অতএব ট্রিগার সংস্করণটি যা দেখতে এরকম দেখাচ্ছে:

CREATE OR REPLACE FUNCTION set_doc_revision()
RETURNS TRIGGER AS $$ BEGIN
  WITH version AS (
    INSERT INTO document_revision_counters (document_id, counter) VALUES (NEW.document_id, 1)
    ON CONFLICT (document_id)
    DO UPDATE SET counter = document_revision_counters.counter + 1
    RETURNING counter
  )

  SELECT INTO NEW.rev counter FROM version; RETURN NEW; END;
$$ LANGUAGE 'plpgsql';

CREATE TRIGGER set_doc_revision BEFORE INSERT ON document_revisions
FOR EACH ROW EXECUTE PROCEDURE set_doc_revision();

এটি INSERTনির্ধারিত INSERTউত্স থেকে উত্পন্ন উত্সর মুখোমুখি হয়ে আরও অনেক সোজা এগিয়ে এবং ডেটার অখণ্ডতা আরও দৃ rob় করে তোলে :

scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'baz');
INSERT 0 1

scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'foo');
INSERT 0 1

scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'bar');
INSERT 0 1

scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (42, 'meaning of life');
INSERT 0 1

scratch=# SELECT * FROM document_revisions;
 document_id | rev |   other_data    
-------------+-----+-----------------
           1 |   1 | baz
           1 |   2 | foo
           1 |   3 | bar
          42 |   1 | meaning of life
(4 rows)
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.