RE:NODE

ბაზები12 წუთის საკითხავი

MySQL-ის ტრანზაქციები, locking და deadlock-ები განმარტებით

როგორ იქცევა სინამდვილეში InnoDB-ის იზოლაციის დონეები, row და gap lock-ები, რატომ ხდება deadlock-ები და lock wait timeout-ები, როგორ იპოვო დამბლოკავი და როგორ გაიმეორო უსაფრთხოდ.

0 მკითხველი

deadlock MySQL-ში არც crash-ია და ჩვეულებრივ არც ბაზის ბაგი. ეს არის InnoDB, რომელიც ამჩნევს, რომ ორი ტრანზაქცია თითოეული ელოდება lock-ს, რომელიც მეორეს უჭირავს, ირჩევს ერთს უკან დასაბრუნებლად და შენს აპლიკაციას ეუბნება, თავიდან სცადოს - შეცდომა 1213, Deadlock found when trying to get lock; try restarting transaction. შეტყობინება პირდაპირი რჩევაა. აპლიკაციები, რომლებიც მას გამეორებით უმკლავდებიან და ტრანზაქციებს მოკლედ ინახავენ, deadlock-ებს თითქმის ვერც ამჩნევენ. იტანჯებიან ისინი, ვისაც გრძელი ტრანზაქციები აქვს, განახლებები, რომლებიც scan-ავენ ხაზებს, რომლებსაც არ ცვლიან, და გამეორება საერთოდ არ აქვთ. ეს პოსტი ხსნის, რას ბლოკავს სინამდვილეში InnoDB, როგორ განსხვავდება იზოლაციის დონეები პრაქტიკაში და როგორ იპოვო და გაასწორო ლოდინები და deadlock-ები, რომლებსაც აწყდები.

ტრანზაქციები და autocommit#

InnoDB ტრანზაქციულია, და MySQL ნაგულისხმევად autocommit = 1-ით მუშაობს: ყოველი ბრძანება თავისთავად ტრანზაქციაა, რომელიც დასრულებისას commit-დება. ბრძანებების დასაჯგუფებლად ტრანზაქცია ცხადად დაიწყე.

sql
START TRANSACTION;UPDATE accounts SET balance = balance - 25 WHERE id = 1;UPDATE accounts SET balance = balance + 25 WHERE id = 2;COMMIT;   -- or ROLLBACK;

ტრანზაქციის შიგნით ჩაწერების მიერ აღებული lock-ები COMMIT-მდე ან ROLLBACK-მდე ნარჩუნდება და არა ბრძანების დასრულებამდე. ეს ერთი ფაქტი locking-ის პრობლემების უმეტესობას ხსნის: ყოველი წამი, რომელსაც ტრანზაქცია ღიად ატარებს, წამია, როცა მისი lock-ები ყველა დანარჩენს ბლოკავს.

რამ, რაც ტრანზაქციას შენი თხოვნის გარეშე ამთავრებს:

  • DDL და ზოგიერთი ადმინისტრაციული ბრძანება - CREATE, ALTER, DROP, TRUNCATE, RENAME, LOCK TABLES და სხვა ტრანზაქციის დაწყება - იმპლიციტურად commit-ს აკეთებს. MySQL-ში სქემის ცვლილების უკან დაბრუნება შეუძლებელია, და migration, რომელიც DDL-სა და მონაცემების ცვლილებებს ერთ "ტრანზაქციაში" ურევს, ატომური არ არის.
  • გათიშვა ყველაფერს, რაც commit-ირებული არ არის, უკან აბრუნებს.
  • deadlock მთელ ტრანზაქციას უკან აბრუნებს.

SAVEPOINT name და ROLLBACK TO SAVEPOINT name ტრანზაქციის ნაწილს აუქმებს მისი დასრულების გარეშე, და სწორედ ასე ახორციელებენ ORM-ები ჩადგმულ ტრანზაქციებს.

იზოლაციის დონეები და რას ნიშნავს ისინი InnoDB-ში#

იზოლაციის დონე წყვეტს, რას ხედავს ტრანზაქცია სხვა ტრანზაქციების ცვლილებებიდან. MySQL-ის ნაგულისხმევია REPEATABLE READ, განსხვავებით PostgreSQL-ისა და SQL Server-ისგან, რომელთა ნაგულისხმევიც READ COMMITTED-ია.

დონერას ხედავს უბრალო SELECTlocking-ის ქცევა
READ UNCOMMITTEDჯერ commit-ის გარეშე დარჩენილ ცვლილებებს (dirty reads)როგორც READ COMMITTED
READ COMMITTEDახალ snapshot-ს ყოველ ბრძანებაზეrecord lock-ები; gap lock-ები უმეტესად გამორთულია
REPEATABLE READ (ნაგულისხმევი)ერთ snapshot-ს მთელი ტრანზაქციისთვისnext-key lock-ები locking წაკითხვებსა და ჩაწერებზე
SERIALIZABLEროგორც REPEATABLE READ, მაგრამ უბრალო SELECT-ები FOR SHARE ხდებაყველაზე მეტი locking, ყველაზე მეტი ლოდინი

InnoDB-ში უბრალო SELECT ბრძანებები lock-ებს საერთოდ არ იღებენ. ისინი თანმიმდევრული snapshot-იდან კითხულობენ multi-version concurrency control-ის გამოყენებით: ხაზების ძველი ვერსიები undo log-ში ინახება, ასე რომ მკითხველი მონაცემებს თავისი snapshot-ის მომენტისას ხედავს, ხოლო ჩამწერები მუშაობას აგრძელებენ. REPEATABLE READ-ში snapshot ტრანზაქციის პირველ წაკითხვაზე აიღება (ან START TRANSACTION WITH CONSISTENT SNAPSHOT-ზე) და ბოლომდე ნარჩუნდება, ასე რომ ერთი და იგივე query ყოველ ჯერზე ერთსა და იმავე ხაზებს აბრუნებს - მაშინაც კი, თუ სხვა ტრანზაქციებმა ამასობაში ცვლილებები commit-ეს.

sql
-- Check and change for this sessionSELECT @@transaction_isolation;SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;-- Or for the next transaction onlySET TRANSACTION ISOLATION LEVEL READ COMMITTED;

ბევრი აპლიკაცია READ COMMITTED-ზე სავსებით კარგად მუშაობს, და ის ნაკლებ gap lock-ს იღებს, რაც insert-ით დატვირთულ ცხრილებზე ნაკლებ lock-ის ლოდინსა და ნაკლებ deadlock-ს ნიშნავს. ეს გონივრული ცვლილებაა, თუ განზრახ აკეთებ; ის არ არის გამოსწორება, რომელიც ბრმად უნდა გააკეთო, რადგან კოდი, რომელიც ტრანზაქციის შიგნით სტაბილურ snapshot-ს ეყრდნობოდა, სხვანაირად მოიქცევა.

ხანგრძლივ ტრანზაქციას ფასი აქვს, მაშინაც კი, თუ მხოლოდ კითხულობს: InnoDB ვერ წმენდს ხაზების ძველ ვერსიებს, რომლებიც მის snapshot-ს შეიძლება ჯერ კიდევ სჭირდებოდეს, ამიტომ undo-ს ისტორია იზრდება. SHOW ENGINE INNODB STATUS ამას History list length-ად აჩვენებს; მილიონებში რიცხვი ნიშნავს, რომ რაღაცას ტრანზაქცია ძალიან დიდი ხანია ღია აქვს.

locking წაკითხვები და დაკარგული განახლება#

რადგან უბრალო წაკითხვები არ ბლოკავს, კლასიკური read-modify-write არაუსაფრთხოა:

sql
-- Two requests run this at the same time for the same accountSELECT balance FROM accounts WHERE id = 1;          -- both read 100UPDATE accounts SET balance = 75 WHERE id = 1;      -- both write 75

ორივე მოთხოვნამ 100 დაინახა და ორივემ 75 ჩაწერა; ერთი თანხის გამოტანა გაქრა. სამი გამოსავალი, საუკეთესოდან ყველაზე ზოგადამდე:

  1. არითმეტიკა UPDATE-ში გააკეთე. UPDATE accounts SET balance = balance - 25 WHERE id = 1 AND balance >= 25; ატომურია, და rowCount() = 0 გეუბნება, რომ ნაშთი არასაკმარისი იყო.
  2. ხაზი წაკითხვისას დაბლოკე. SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; ექსკლუზიურ lock-ს იღებს, ასე რომ მეორე მოთხოვნა ელოდება, სანამ პირველი commit-ს გააკეთებს, და შემდეგ ახალ მნიშვნელობას კითხულობს.
  3. ოპტიმისტური locking. დაამატე version სვეტი, განაახლე WHERE id = 1 AND version = 7-ით და გაიმეორე, თუ არცერთი ხაზი არ დაემთხვა. lock-ები არ ნარჩუნდება, სანამ მომხმარებელი ფიქრობს.

FOR SHARE (LOCK IN SHARE MODE-ის 8.0-ის დაწერილობა) გაზიარებულ lock-ს იღებს: სხვებსაც შეუძლიათ ხაზის წასაკითხად დაბლოკვა, მაგრამ ვერავინ შეცვლის მას, სანამ commit-ს არ გააკეთებ. ორი მოდიფიკატორი locking წაკითხვებს სამუშაო რიგებისთვის სასარგებლოს ხდის:

sql
-- Fail immediately instead of waitingSELECT * FROM jobs WHERE id = 42 FOR UPDATE NOWAIT;-- Claim the next free job, skipping rows other workers have lockedSTART TRANSACTION;SELECT id, payload FROM jobsWHERE status = 'pending'ORDER BY id LIMIT 1FOR UPDATE SKIP LOCKED;UPDATE jobs SET status = 'running' WHERE id = ?;COMMIT;

SKIP LOCKED ბევრ worker-ს საშუალებას აძლევს, ერთი ცხრილიდან აიღონ სამუშაო ერთმანეთის უკან რიგში დგომის გარეშე, და სწორედ ასე რჩება ბაზაზე დაფუძნებული job-ების რიგი სწრაფი.

რას ბლოკავს სინამდვილეში InnoDB#

InnoDB ბლოკავს index-ის ჩანაწერებს და არა ხაზებს აბსტრაქტულად. ეს ამ პოსტში ყველაფერზე მეტად მნიშვნელოვანია.

  • Record lock: lock ერთ index ჩანაწერზე.
  • Gap lock: lock ორ index ჩანაწერს შორის არსებულ ღრეჩოზე, რომელიც მასში insert-ს აჩერებს. გამოიყენება REPEATABLE READ-ში, რომ შენ მიერ დაბლოკილ დიაპაზონში phantom ხაზები არ გამოჩნდეს.
  • Next-key lock: record lock პლუს მის წინ არსებული ღრეჩო. ამას იყენებს REPEATABLE READ locking წაკითხვებისთვის და UPDATE-ისა და DELETE-ისთვის, რომლებიც დიაპაზონით ან არაუნიკალური index-ით ეძებენ.
  • Insert intention lock: სპეციალური gap lock, რომელსაც INSERT იღებს. ორი insert ერთსა და იმავე ღრეჩოში სხვადასხვა პოზიციაზე ერთმანეთს არ ბლოკავს, მაგრამ insert ელოდება ნებისმიერ gap lock-ს, რომელიც სხვას უჭირავს.

შედეგი: locking ბრძანება ბლოკავს ყოველ index ჩანაწერს, რომელსაც ამოწმებს, და არა მხოლოდ იმებს, რომლებსაც ცვლის. გამოსადეგი index-ის გარეშე ის მთელ ცხრილს ამოწმებს - და ბლოკავს.

sql
-- No index on email: scans and locks every row in usersUPDATE users SET last_seen = NOW() WHERE email = 'a@example.com';-- With an index: locks one record (and a gap under REPEATABLE READ)CREATE INDEX idx_users_email ON users (email);

პირველი ვერსია ორ ერთმანეთთან დაუკავშირებელ მომხმარებელს, რომლებიც ერთდროულად შედიან, lock-ის ლოდინად აქცევს. index UPDATE-ის, DELETE-ისა და SELECT ... FOR UPDATE-ის WHERE პირობების სვეტებზე ისევე locking-ის გამოსწორებაა, როგორც სიჩქარის. MySQL-ის index-ები და EXPLAIN აჩვენებს, როგორ შეამოწმო, რას გაივლის ბრძანება scan-ით.

foreign key-ებიც lock-ებს ამატებს: შვილი ხაზის insert იღებს გაზიარებულ lock-ს მშობელ ხაზზე, რომელსაც მიუთითებს, ხოლო insert-ზე unique index-ის შემოწმება გაზიარებულ lock-ს იღებს ნაპოვნ დუბლიკატზე. ორივე ჩნდება deadlock-ის ანგარიშებში და აკვირვებს ხალხს, ვისაც locking წაკითხვა არასოდეს დაუწერია.

Lock wait timeout-ები: დამბლოკავის პოვნა#

როცა ტრანზაქცია lock-ს innodb_lock_wait_timeout-ზე მეტხანს ელოდება - ნაგულისხმევად 50 წამი - ის იღებს შეცდომას 1205, Lock wait timeout exceeded; try restarting transaction.

lock-ის ლოდინს ყოველთვის სხვა ტრანზაქცია იწვევს, რომელსაც lock უჭირავს - ჩვეულებრივ ისეთი, რომელიც ზედმეტად დიდხანს არის ღია. sys სქემა აჩვენებს, ვინ ვის ბლოკავს:

sql
SELECT wait_age, locked_table, locked_index,       waiting_pid, waiting_query,       blocking_pid, blocking_query,       sql_kill_blocking_connectionFROM sys.innodb_lock_waits;

ცარიელი blocking_query ყველაზე გავრცელებული და ყველაზე მეტყველი შედეგია: დამბლოკავი session არაფერს უშვებს. მან ტრანზაქცია დაიწყო, ხაზი შეცვალა და უმოქმედო გახდა - მოთხოვნა, რომელიც rollback-ის გარეშე ჩავარდა, მაგრამ pool-ის კავშირი შეინარჩუნა, დეველოპერის SQL კლიენტი გამორთული autocommit-ით, ან აპლიკაცია, რომელიც ტრანზაქციის შიგნით HTTP გამოძახებას აკეთებს. გაარკვიე, რამდენ ხანს არის ტრანზაქციები ღია:

sql
SELECT trx_mysql_thread_id AS pid, trx_state, trx_started,       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS open_s,       trx_rows_locked, trx_rows_modified, trx_queryFROM information_schema.innodb_trxORDER BY trx_started;

KILL <pid> session-ს ამთავრებს და მის ტრანზაქციას უკან აბრუნებს. შემდეგ მიზეზი გაასწორე - ტრანზაქცია არასოდეს უნდა დარჩეს ღია, სანამ კოდი ბაზის გარდა რაიმე სხვას ელოდება. performance_schema.data_locks და data_lock_waits ყოველ ცალკეულ lock-ს აჩვენებს, თუ დეტალები გჭირდება.

Deadlock-ები: რატომ ხდება და როგორ წაიკითხო#

deadlock ციკლია: ტრანზაქცია A-ს უჭირავს lock, რომელიც B-ს უნდა, ხოლო B-ს უჭირავს lock, რომელიც A-ს უნდა. ვერცერთი ვერ გააგრძელებს, ამიტომ ლოდინს აზრი არ აქვს. InnoDB-ის deadlock-ის დეტექტორი (innodb_deadlock_detect, ნაგულისხმევად ჩართული) ციკლს მყისიერად ამჩნევს, უკან აბრუნებს ტრანზაქციას, რომელმაც ყველაზე ცოტა ხაზი შეცვალა, და აბრუნებს შეცდომას 1213 SQLSTATE 40001-ით. timeout-ისგან განსხვავებით, მსხვერპლი ტრანზაქცია მთლიანად ბრუნდება უკან.

უჭირავსუჭირავსელოდებაელოდება, ციკლიტრანზაქცია 1ბლოკავს შეკვეთა 10-სტრანზაქცია 2ბლოკავს შეკვეთა 20-სხაზი id 10ხაზი id 20
ორი ტრანზაქცია ერთსა და იმავე ხაზებს საპირისპირო რიგით ანახლებს

გავრცელებული შაბლონები:

  • საპირისპირო რიგი. კოდის ერთი გზა ჯერ შეკვეთა 10-ს ანახლებს, შემდეგ 20-ს, მეორე - ჯერ 20-ს, შემდეგ 10-ს. გამოსავალი: ხაზები ყოველთვის თანმიმდევრული რიგით დაბლოკე, ჩვეულებრივ primary key-ით - დაალაგე ID-ები ციკლამდე.
  • შემოწმება, შემდეგ insert. ორი session უშვებს SELECT ... FOR UPDATE-ს ხაზისთვის, რომელიც ჯერ არ არსებობს (ორივე თავსებად gap lock-ს იღებს), შემდეგ ორივე INSERT-ს აკეთებს (თითოეული insert intention მეორის gap lock-ს ელოდება). გამოსავალი: გადაწყვეტა unique index-ს მიანდე - INSERT ... ON DUPLICATE KEY UPDATE, ან insert და 1062-ის დაჭერა.
  • ფართო scan-ები. UPDATE ცუდად დაინდექსებული WHERE-ით ბევრად მეტ ხაზს ბლოკავს, ვიდრე ცვლის, ამიტომ ყველას ეფარება. გამოსავალი: index.
  • გრძელი ტრანზაქციები. რაც უფრო დიდხანს უჭირავს ტრანზაქციას lock-ები, მით უფრო სავარაუდოა, რომ სხვა ტრანზაქციას რომელიმე მათგანი არასწორი რიგით დასჭირდება.

უახლესი deadlock სრულად აღწერილია SHOW ENGINE INNODB STATUS\G-ში, LATEST DETECTED DEADLOCK სექციაში: ორივე ტრანზაქცია, ბრძანება, რომელსაც თითოეული უშვებდა, დაკავებული lock-ები და lock, რომელსაც ელოდებოდა, index-ის სახელის ჩათვლით. მხოლოდ ბოლო ინახება. ყოველი deadlock-ის error log-ში ჩასაწერად ჩართე innodb_print_all_deadlocks, რომელსაც ადმინისტრაციული ანგარიში სჭირდება:

sql
SET PERSIST innodb_print_all_deadlocks = ON;

წაიკითხე HOLDS THE LOCK(S) და WAITING FOR THIS LOCK TO BE GRANTED ხაზები თითოეული ტრანზაქციისთვის; იქ დასახელებული index გეუბნება, რომელ წვდომის გზას უნდა შეხედო. lock_mode X locks gap before rec და insert intention ერთად თითქმის ყოველთვის შემოწმება-შემდეგ-insert შაბლონია.

სწორად გამეორება#

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

python
import timeimport pymysqlRETRYABLE = {1213, 1205}  # deadlock, lock wait timeoutdef run_in_transaction(conn, work, attempts=3):    for attempt in range(1, attempts + 1):        try:            conn.begin()            result = work(conn)            conn.commit()            return result        except pymysql.err.OperationalError as e:            conn.rollback()            if e.args[0] not in RETRYABLE or attempt == attempts:                raise            time.sleep(0.05 * attempt)  # short, growing backoff

გასამეორებელი ფუნქცია ბაზის გარეთ გვერდითი ეფექტებისგან თავისუფალი დატოვე - მის შიგნით email-ის გაგზავნა ან ბარათიდან თანხის ჩამოჭრა ნიშნავს, რომ ამას ორჯერ გააკეთებ. Laravel-ის DB::transaction($callback, 3) და Django-ს შაბლონები transaction.atomic()-ის გარშემო იმავე სტრუქტურას იძლევა; ნედლ PDO-ში შეამოწმე $e->errorInfo[1] 1213-ზე და 1205-ზე, როგორც PHP PDO და MySQL-შია.

ჩვევები, რომლებიც lock-ების პრობლემების უმეტესობას აცილებს#

პატარა MySQL სერვერზე lock-ის თითქმის ყოველი პრობლემა რამდენიმე ჩვევაზე დადის. არცერთს კონფიგურაციის ცვლილება არ სჭირდება:

  1. ტრანზაქციები მოკლე შეინარჩუნე. გახსენი ტრანზაქცია, გაუშვი ბრძანებები, გააკეთე commit. მის შიგნით არასოდეს გამოიძახო გარე API, არ გააგზავნო email, არ დაელოდო მომხმარებელს და არ დააძინო. ჯერ ნელი სამუშაო გააკეთე, შემდეგ ბაზის სამუშაო.
  2. დააინდექსე ის, რასაც ბლოკავ. ყოველმა UPDATE-მა, DELETE-მა და SELECT ... FOR UPDATE-მა თავისი ხაზები index-ით უნდა იპოვოს, რომ ბლოკოს ის ხაზები, რომლებიც სჭირდება, და არა ცხრილი.
  3. ბლოკე თანმიმდევრული რიგით. როცა ტრანზაქცია რამდენიმე ხაზს ეხება, ჯერ ისინი primary key-ით დაალაგე. ორ ტრანზაქციას, რომლებიც ყოველთვის ერთი რიგით ბლოკავს, ამ ხაზებზე deadlock ვერ მოუვა.
  4. ატომური ბრძანებები ამჯობინე. UPDATE ... SET stock = stock - 1 WHERE stock > 0 სჯობს წაკითხვას, შემოწმებასა და ჩაწერას.
  5. unique index-ებს მიეცი არბიტრობის საშუალება. ჯერ შემოწმების ნაცვლად გააკეთე insert და დაამუშავე შეცდომა 1062.
  6. დიდი ცვლილებები ნაწილებად დაყავი. ორი მილიონი ძველი ხაზის ერთი ბრძანებით წაშლა ორ მილიონ lock-ს ინარჩუნებს და undo log-ს ბერავს; ციკლში 5,000-ის ერთდროულად წაშლა, თითოეული საკუთარ ტრანზაქციაში, სხვა სამუშაოს ნაწილებს შორის გატარების საშუალებას აძლევს.
  7. გაიმეორე 1213 და 1205 მთელი ტრანზაქციით, რამდენჯერმე, მოკლე backoff-ით.

ნაწილებად დაყოფა მაგალითს იმსახურებს, რადგან სწორედ მას ტოვებს ხალხი:

sql
-- Repeat until it affects 0 rowsDELETE FROM events WHERE created_at < '2026-01-01' ORDER BY id LIMIT 5000;

Metadata lock-ები: როცა ALTER TABLE ყველაფერს აჩერებს#

InnoDB-ის row lock-ების ზემოთ მეორე lock-ების სისტემაა. ყოველი ბრძანება, რომელიც ცხრილს ეხება, მასზე metadata lock-ს იღებს, რომელიც ტრანზაქციის დასრულებამდე ნარჩუნდება. ALTER TABLE-ს ექსკლუზიური metadata lock სჭირდება, ამიტომ ის ელოდება ყოველ ღია ტრანზაქციას, რომელიც ცხრილს შეეხო - და ცხრილზე ყოველი ახალი query რიგში დგება მომლოდინე ALTER-ის უკან. ერთ უმოქმედო ტრანზაქციას და ერთ migration-ს შეუძლია მთელი საიტი გააჩეროს, სადაც ყოველი query აჩვენებს Waiting for table metadata lock-ს.

lock_wait_timeout, რომელიც metadata lock-ებს მართავს, ნაგულისხმევად 31,536,000 წამია - ერთი წელი. ცოცხალ სისტემაზე DDL-ის გაშვებამდე შენი session-ისთვის ის დაბლა დააყენე, რომ migration-მა უარი თქვას და ტრაფიკი არ გაყინოს:

sql
SET SESSION lock_wait_timeout = 5;ALTER TABLE orders ADD COLUMN note VARCHAR(255) NULL, ALGORITHM=INSTANT;

თუ ჩავარდა, იპოვე უმოქმედო ტრანზაქცია ზემოთ მოცემული innodb_trx query-ით (ან sys.schema_table_lock_waits-ით), მოაგვარე და კიდევ სცადე. Migration-ები downtime-ის გარეშე ამ პროცესის დანარჩენ ნაწილს განიხილავს.

FAQ#

deadlock-ები იმის ნიშანია, რომ რაღაც გაფუჭებულია?

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

გადავიდე READ COMMITTED-ზე?

თუ შენი დატვირთვა insert-ით მძიმეა და gap lock-ის deadlock-ებს ხედავ, ეს ხშირად ეხმარება, და ბევრი framework მასზეა გატესტილი. შეცვალე session-ზე ან გლობალურად, შემდეგ გატესტე კოდის გზები, რომლებიც ერთ ტრანზაქციაში ერთსა და იმავე მონაცემებს ორჯერ კითხულობენ. ნუ ელი, რომ ის გრძელი ტრანზაქციებით გამოწვეულ lock-ის ლოდინებს გაასწორებს; მათ მოკლე ტრანზაქციების გარდა არაფერი ასწორებს.

გავზარდო innodb_lock_wait_timeout?

იშვიათად. ორმოცდაათი წამი უკვე მეტია, ვიდრე ნებისმიერმა ვებ მოთხოვნამ უნდა ილოდინოს. ინტერაქტიული დატვირთვისთვის შეამცირე (5-10 წამი), რომ გაჭედილი მოთხოვნები სწრაფად ჩავარდეს, და გაზარდე მხოლოდ batch job-ებისთვის, რომლებიც შეგნებულად ელოდებიან სხვა batch job-ებს.

ბლოკავს SELECT UPDATE-ს MySQL-ში?

უბრალო SELECT - არა; ის snapshot-ს კითხულობს და row lock-ებს არ იღებს. SELECT ... FOR UPDATE და FOR SHARE - კი, ხოლო ყოველი SELECT ცხრილზე გაზიარებულ metadata lock-ს იღებს, რომელიც ALTER TABLE-ს ბლოკავს, მაგრამ არა UPDATE-ს.

შემიძლია deadlock-ის დეტექციის გამორთვა?

კი, innodb_deadlock_detect = OFF-ით, რის შემდეგაც deadlock-ები innodb_lock_wait_timeout-ით წყდება. ეს არსებობს ძალიან მაღალი პარალელურობის სისტემებისთვის, სადაც თავად დეტექცია ხდება ვიწრო ადგილი. პატარა სერვერზე ჩართული დატოვე: მყისიერი დეტექცია 50-წამიან ლოდინს სჯობს.


კომენტარები

სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.

0/2000