დამოუკიდებელი ტრანზაქციები

ტრანზაქციების ქვეშ ვგულისხმობ მონაცმებთა ბაზის ტრანზაქციებს, რომლებიც სხვადასხვა ოპერაციებს აერთიანებენ და ACID თვისებების დაცვით ბაზაში არსებული მონაცემების კორექტულობას უზრუნველყოფენ. ასეთი ტრანზაქციების მხარდაჭერა დღეს მგონი ყველა გავრცელებულ რელაციურ მონაცემთა ბაზას აქვს.

როდესაც ისეთი ოპერაციების ჩატარება გვინდა ბაზის მონაცემებზე, ან მრავალი ფუნქციის და პროცედურის გამოძახება, რომლებიც ერთმანეთზეა დამოკიდებული და თუ ერთგან მაინც რაიმე შეცდომა მოხდა, ყველაფერი უკან უნდა დაბრუნდეს, ყველა insert, update და delete უნდა გაუქმდეს, ასეთ შემთხვევაში ამ ყველაფერს ერთ ტრანზაქციაში მოვაქცევთ. მუშაობის დასრულების ბოლოს კი, იმის მიხედვით ყველაფერი წარმატებით შესრულდა თუ არა, ტრანზაქციას დავა-commit-ებთ, ან rollback ბრძანებით გავაუქმებთ. ამისთვის საჭიროა, რომ მთელი პროცესის განმავლობაში შეცდომის ჰენდლინგი სწორად გავაკეთოთ, მოვიფიქროთ სად დავიჭიროთ და დავამუშაოთ ხოლმე exception და სად გავუშვათ სულ გარეთ, მანამ სანამ სულ გარე ფუნქციაში არ გავა.

ანუ განვსაზღვროთ როდის უნდა დასრულდეს პროცესი  წარმატებით და როდის უნდა შეჩერდეს. შესაბამისად, ამ კონკრეტული პირობების შემთხვევაში ან სამუდამოდ შევინახავთ ცვლილებებს, ან გავაუქმებთ.

საქმე იმაშია, რომ ზოგჯერ გვჭირდება პროცესის შიგნით ზოგიერთი ნაწილი უპირობოდ დავაკომიტოთ, იმის მიუხედავად ტრანზაქცია წარმატებით დასრულდა თუ არა. მაგალითად, თუ გზადაგზა სხვადასხვა ფუნქციაში მომხდარ შეცდომებს ცხრილში ვინახავთ, გვინდა რომ ისინი ყველა შემთხვევაში შენახული იყოს. მაგრამ როდესაც მათ შესანახად კომიტს გავუშვებთ, ის იმ ტრანზაქციის ფარგლებში შესრულებულ ყველა ოპერაციას დააკომიტებს, ამიტომ სხვა გზა უნდა ვიპოვოთ.

თუ ასეთი სახის ამოცანა ორაკლში უნდა გადავჭრათ, მაშინ შეგვიძლია ზოგიერთი ტრანზაქცია მთავარი ტრანზაქციიდან დამოუკიდებლად გავუშვათ. ამისათვის ჩვენი ქვეპროგრამის declaration სექციაში (სადაც სხვადასხვა ცვლადების ან ქვეპროგრამების აღწერა ხდება), უნდა ჩავუწეროთ pragma autonomous_transaction;

pragma კომპილატორის დირექტივაა. მისი საშუალებით შეგვიძლია კომპილატორს ინფორმაცია მივაწოდოთ და ამით ქვეპროგრამის მუშაობის გზა შევცვალოთ. ორაკლში ასეთი კიდევ სამი სახის ინსტრუქცია არსებობს exception_init, restrict_references, serially_reusable, თუმცა მათ ახლა არ ჩავუღრმავდები.

მაგალითად, თუ ჩვენს log_error პროცედურაში (რომელიც ზევით არსებულ პროცესის ნაწილია და გზადაგზა შეცდომებს ინახავს ცხრილში) ჩავამატებთ autonomous_transaction პრაგმას, მასში გამოძახებული commit და rollback ბრძანებებს არ ექნება გავლენა მთავარ ტრანზაქციაზე და მხოლოდ ქვეპროგრამის ცვლილებებს შეინახავს. ცხადია, ეს დამოუკიდებლობა ვრცელდება მთავარ ტრანზაქციასთან დაკავშირებულ რესურსებსა და lock-ებზეც.

pragma autonomous_transaction-ით შეგვიძლია ანალოგიური შესაძლებლობა მივცეთ ორაკლის ტრიგერს, რომელშიც ჩვეულებრივ არ შეიძლება დაკომიტება, რადგან ის აღმძვრელ ოპერაციაზეა დამოკიდებული (ანუ რის გამოც მოხდა ტრიგერის გამოძახება). შესაბამისად, თუ ეს უკანასკნელი დაკომიტდა, ჩვეულებრივი ტრიგერიც დაკომიტდება და ასევე როლბექის შემთხვევაში.

დამოუკიდებელ ტრიგერში ამის გარდა შესაძლებელი გახდება DDL ბრძანებების შესულება (ცხრილის შექმნა, ცვლილება, ა.შ.).

MS SQL მონაცემთა ბაზაში, როგორც ჩანს, არ არსებობს ასეთი დამოუკიდებელი ტრანზაქციების ანალოგი (ყოველ შემთხვევაში ძველ ვერსიებში არ ჰქონდათ მისი მხარდაჭერა), მაგრამ არსებობს შემოვლითი გზები, რითიც ასეთი მიზნის მიღწევა შეიძლება.

MySQL-საც არ აქვს დამოუკიდებელი ტრანზაქციების ცნება, თუმცა აქ საქმე ცოტა სხვაგვარადაა. ზევით ნახსენებ ბაზებისგან განსხვავებით MySQL-ში საერთოდ ტრანზაქციების არსებობა მის storage engine-ებზეა დამოკიდებული. ასე რომ, თუ ვთქვათ შეცდომების ლოგირება გვინდა რომელიმე ცხრილში, შეგვიძლია იმ ცხრილისთვის MyISAM ძრავი გამოვიყენოთ რომელსაც არ აქვს ტრანზაქციების მხარდაჭერა, და როლბექის დროს ამ ცხრილში არსებულ ჩანაწერებს არაფერი მოუვა.

ბიტური ოპერაციები ორაკლში

დღეს ზედმეტი მოვინდომე და ბაზაში მონაცემების ბიტმასკების სახით შენახვა გადავწყვიტე, რომელიც მერე ისევ თვითონ ბაზას უნდა დაემუშავებინა. შემდეგ ვნახე, რომ ორაკლში არ არის რეალიზებული ბიტური ოპერაციები და არის მხოლოდ ერთი – bitand() ფუნქცია, რომელიც ბიტურ “and”-ს აკეთებს რიცხვებზე.
“or” და “xor”-ის მიღებაც ამ ფუნქციის გამოყენებით მარტივად შეიძლება. მაგალითად a და b რიცხვებზე “or” ოპერაცია გამოვა

a + b - bitand(a,b)

ხოლო “xor”-ს მივიღებთ თუ კიდევ ერთხელ გამოვაკლებთ:

a + b - bitand(a,b) - bitand(a,b)

არც ბიტური წაძვრები (shift >>) აქვს, მაგრამ მაგის ჩანაცვლება 2-ზე გამრავლებით და გაყოფით შეიძლება.

MySQL-ში არ დამჭირვებია, მაგრამ ეხლა შევხედე და ჰქონია პირდაპირ &, |, ~, ^, <<, >> და თან bit_count() ფუნქციაც, რომელიც ჩართულ ბიტების რაოდენობას აბრუნებს. მათი გამოყენება პირდაპირ sql ქვერებშიც შეიძლება.

ბოლოს დამაინტერესა და სხვა ბაზებსაც გადავწვდი. PostgreSQL-ში უკანასკნელის მსგავსად ყველა არის რეალიზებული, MS SQL-ში კი მხოლოდ and, or და xor არიან.

პრინციპში დიდი გამოყენება ალბათ არ აქვთ, მაგრამ ზოგ შემთხვევაში ძალიან მოსახერხებელი გამოდის. მაგალითად, თუ ობიექტთან დაკავშირებული სტატიკური (თუნდაც არასტატიკური) პატარა სიმრავლის შენახვაა საჭირო, ანუ ამ ობიექტისთვის სიმრავლეში რომელი ელემენტებია ”ჩართული” და რომელი არა.

Case sensitivity MySQL-ში

ორ ასოზე მეტის დაწერა ქართულად ვერ მოვახერხე.. 😀

ბოლო დროს ერთ პრობლემაზე იმდენი დრო დავკარგე, რომ დამამახსოვრდა. სინამდვილეში არაკორექტული ლოგირების გამო უფრო იყო, მაგრამ ეს არაა ახლა მთავარი. MySQL-ში როცა რამეს აღვწერთ, თითქმის ყველაფერი case-insensitive არის, ანუ არ აქვს მნიშვნელობა ველის სახელს დიდი ასოებით დავწერთ თუ პატარით.

კონკრეტულად, არ აქვს მნიშვნელობა ველის, ფუნქციის, პროცედურის, ინდექსის და ალიასის შემთხვევაში. ტრიგერი ამ მხრივ გამონაკლისია.

ხოლო რაც შეეხება ცხრილის და ბაზის სახელებს – მათი ქეის სენსითივობა ოპერაციულ სისტემაზეა დამოკიდებული.

კი, ვერ მივხვდი ლინუქსში რატომ აღარ მუშაობდა 😀

MySQL-ში მონაცემთა ბაზები დირექტორიებს შეესაბამება, ცხრილი კი მინიმუმ ერთ ფაილს ბაზის დირექტორიაში. ეს ნიშნავს რომ ვინდოუსის შემთხვევაში რეგისტრს მნიშვნელობა არ აქვს, იუნიქსის პლატფორმაზე კი აქვს.

შეიძლება lower_case_table_names პარამეტრის გამოყენებაც, თუმცა ამას ყველაფერი პატარა რეგისტრში გადაჰყავს და თუ მიგრაციას აკეთებთ, მაშინ არსებული ცხრილების და ბაზების სახელების კონვერტაცია იქნება საჭირო წინასწარ.

შემთხვევითი ჩანაწერის ამორჩევა მონაცემთა ბაზის ცხრილიდან

ალბათ ბევრგან დაგჭირვებიათ შემთხვევითი ჩანაწერის ამოღება (random წერილის გამოყვანა ბლოგზე, random ფრაზა, random წიგნი – ბიბლიოთეკის საიტზე, random პროდუქტი – მაღაზიის საიტზე, ა.შ.) და შეიძლება რაიმე კარგი მეთოდიც გაქვთ შერჩეული ამისთვის.
მე კვლევები არ მიწარმოებია :))) ამ საკითხზე. უბრალოდ შემხვდა სასარგებლო სტატია, სადაც ამ მეთოდების განხილვაა მოყვანილი და მათი სისწრაფეებია შედარებული.

მეთოდი 1:

ვიყენებთ RAND() ფუნქციას, რომელიც float ტიპის რიცხვს აბრუნებს 0-დან 1-მდე.
sql მოთხოვნას შემდეგი სახე აქვს: SELECT * FROM `table` ORDER BY RAND() LIMIT 0,1;

როგორც სტატიის ავტორი წერს, მეთოდის პრობლემა მისი შესრულების დროა. MySQL-ს ყველა ჩანაწერი დროებით ცხრილში გადააქვს, თითოეულს ანიჭებს რაღაც შემთხვევით ინდექსს სორტირებისთვის. შემდეგ შედეგებს ასორტირებს და აბრუნებს პირველ ჩანაწერს.

უკეთესი მიდგომის აზრი ის არის, რომ ჯერ შეირჩეს შემთხვევითი რიცხვი და ამ შემთხვევითი რიცხვის მიხედვით ამოირჩეს მხოლოდ ერთი ჩანაწერი.

მეთოდი 2:

იმ შემთხვევაში, თუ თითოეულ ჩანაწერს უნიკალური გასაღები (მაგალითად id) გააჩნია, მაშინ შეგვიძლია ავირჩიოთ შემთხვევითი id უმცირეს და უდიდეს id-ებს შორის და შემდეგ დავაბრუნებინოთ იმ არჩეული id-ის ჩანაწერი.

უდიდესი და უმცირესი id-ის გასაგებად MAX() და MIN() ფუნქციები გამოვიყენოთ.

$range_result = mysql_query( " SELECT MAX(`id`) AS max_id , MIN(`id`) AS min_id FROM `table` ");
$range_row = mysql_fetch_object( $range_result );
$random = mt_rand( $range_row->min_id , $range_row->max_id );
$result = mysql_query( " SELECT * FROM `table` WHERE `id` >= $random LIMIT 0,1 ");
მეთოდი საკმაოდ შეზღუდულია, რადგან შეიძლება ცხრილს უნიკალური გასაღები საერთოდ არ ჰქონდეს. ამიტომ მესამე მეთოდში MySQL-ის LIMIT-ს ვეყრდნობით.

მეთოდი 3:

LIMIT ორ არგუმენტს იღებს. მაგალითად LIMIT 5,10  დააბრუნებს ჩანაწერებს 6-დან 15-მდე. ათვლა 0-დან იწყება.  პირველი არგუმენტი offset-ია (არ ვიცი როგორ ითარგმნება), ხოლო მეორე – offset-დან ათვლილი წამოსაღები ჩანაწერების მაქსიმალური რაოდენობა.

რომ გამოვთვალოთ offset პირველ ჩანაწერამდე – დავაგენერიროთ შემთხვევითი რიცხვი MySQL-ის RAND() ფუნქციის საშუალებით. შემდეგ მიღებული რიცხვი გადავამრავლოთ ცხრილში არსებულ ჩანაწერთა რაოდენობას (ამ რაოდენობას COUNT() ფუნქციის მივიღებთ). რადგან LIMIT მხოლოდ მთელ რიცხვებს იღებს არგუმენტებად, ეს ნამრავლი უნდა დავამრგვალოთ – გამოვიყენოთ FLOOR() ფუნქცია.

FLOOR() არითმეტიკული ფუნქციაა, რომელიც გამოთვლის უდიდეს მთელ რიცხვს გადაცემულ პარამეტრამდე. საბოლოოდ, კოდს ასეთი სახე ექნება:

$offset_result = mysql_query( " SELECT FLOOR(RAND() * COUNT(*)) AS `offset` FROM `table` ");

$offset_row = mysql_fetch_object( $offset_result );
$offset = $offset_row->offset;

$result = mysql_query( " SELECT * FROM `table` LIMIT $offset, 1 " );
MySQL 4.1 და უფრო მაღალ ვერსიებში ეს ორი მეთოდი შეგვიძლია ასე გავაერთიანოთ:

მეთოდი 4:

SELECT * FROM `table` WHERE id >= (SELECT CEILING( MAX(id) * RAND()) FROM `table` ) ORDER BY id LIMIT 1;

ამ მეთოდს იგივე ნაკლი აქვს, რაც მეორეს. ის მხოლოდ უნიკალურ გასაღებიან ცხრილებთან მუშაობს.

სტატიის ავტორმა მეთოდების სისწრაფეები შეადარა და სოფტის და ჰარდის სპეციპიკაციებს თუ არ ჩავუღრმავდებით, მიახლოებით ასეთი შედეგი მიიღება:

ყველაზე ნელი პირველი მეთოდია. ვთქვათ, რომ მას დროის 100% დასჭირდა შესრულებისთვის.
მეორე მეთოდს 79%.
მესამეს – 13%.
მეოთხეს – 16%.
გამოდის, რომ ყველაზე სწრაფია მესამე მეთოდი.