Causes and Solutions of MySQL Deadlock

foreword

Recently, Lao Gu often encountered colleagues who said that mysql was deadlocked again, causing business errors . Today we will talk about deadlock and how to solve it

lock type

mysql lock level: page level, table level, row level

Table-level locks: low overhead, fast locking; no deadlocks; large locking granularity, highest probability of lock conflicts, and lowest concurrency.

Row-level locks: high overhead, slow locking; deadlocks may occur; the smallest locking granularity, the lowest probability of lock conflicts, and the highest concurrency.

Page lock: overhead and locking time are between table locks and row locks; deadlocks will occur; locking granularity is between table locks and row locks, and the concurrency is average

Deadlock Causes and Examples

1. Causes:

Deadlock refers to a phenomenon in which two or more processes wait for each other due to competition for resources during execution . If there is no external force, they will not be able to advance. At this time, the system is said to be in a deadlock state Or the system has a deadlock, and these processes that are always waiting for each other are called deadlock processes . Table-level locks will not cause deadlocks. Therefore, the solution to deadlocks is mainly for the most commonly used InnoDB.

The key to deadlock is that the order in which two (or more) Sessions are locked is inconsistent.

Then the key to solving the deadlock problem is to let different sessions lock in order

2. Generate an example:

Case number one

Requirement: Divide the investment money into several shares and distribute them randomly to the borrowers.

At first, the business program idea was as follows:

After the investor invests, the amount is randomly divided into several parts, and then a few are randomly selected from the borrower table, and then the balance in the borrower table is updated through select for update one by one.

For example, two users invest at the same time, and the amount of user A is randomly divided into 2 shares, which are distributed to borrowers 1 and 2.

User B's amount is randomly divided into 2 shares, distributed to borrowers 2, 1

Due to the different order of locking, deadlock will of course appear soon.

The improvement to this problem is very simple, just lock all the allocated borrowers at once.

Select * from xxx where id in (xx,xx,xx) for update

The list values ​​mysql in in will be automatically sorted from small to large, and locks are added one by one from small to large

first session:

Note: Turn off automatic submission set autocommit=0;

mysql> select * from goods where goods_id in (2,3) for update;
+----+--------+------+---------------------+
| good_id | goods_name | price             |
+----+--------+------+---------------------+
|  2 | bbbb     | 1.00 |
|  3 | vvv     | 3.00 |
+----+--------+------+---------------------+

Second session:

select * from goods where goods_id in (3,4,5) for update;

lock waiting...

case two

In development, this kind of judgment requirement is often made: query according to the field value (with index), if it does not exist, insert it; otherwise update.

Taking id as the primary key as an example, there is no row with id=22 yet

Note: Turn off automatic submission set autocommit=0;

first session:

select * from goods where goods_id=22 for update;

Second session:

select * from goods where goods_id=23  for update;

And then in the first session:

insert into goods values(22,'ac',11.5);

lock waiting...

Then to the second session:

insert into goods values(23,'bc',23.0);

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

When locking existing rows (primary key), mysql only has row locks.

When locking a row that does not exist (even if the condition is the primary key), mysql will lock a range

The locked range is:

(infinitely small or less than the maximum value of the locked id in the table, infinite or greater than the minimum value of the locked id in the table)

For example: if there is an existing id in the table (11, 12)

then lock(12, infinity)

Example 2: If the existing id in the table is (11, 30)

Then lock (11, 30)

The solution to this deadlock is:

insert into goods(xx,xx) on duplicate key update `xx`='XX';

Use mysql-specific syntax to solve this problem. Because the insert statement is for the primary key, no matter whether the inserted row exists or not, there will only be row locks.

deadlock check processing

Normally, when a deadlock occurs, the connection with the least weight will be killed and rolled back. But in order to find the statement to optimize, enable the deadlock to record the deadlock information.

#step 1:窗口一
mysql> start transaction;
mysql> update aa set name='aaa' where id = 1;
 
#step 2:窗口二
mysql> start transaction;
mysql> update bb set name='bbb' where id = 1;
 
#step 3:窗口一
mysql> update bb set name='bbb';

View by

#step 4:窗口三
#是否自动提交
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+

view current link

#查看当前连接
mysql> show processlist;
mysql> show full processlist;
mysql> SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST;
+----+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host      | db   | Command | Time | State | Info             |
+----+------+-----------+------+---------+------+-------+------------------+
|  4 | root | localhost | test | Sleep   |  244 |       | NULL             |
|  5 | root | localhost | test | Sleep   |  111 |       | NULL             |
|  6 | root | localhost | NULL | Query   |    0 | init  | show processlist |
+----+------+-----------+------+---------+------+-------+------------------+
  • id column: an identifier, useful when you want to kill a statement.
  • user column: Display the current user, if not root, this command will only display the sql statements within your authority.
  • Host column: It shows which ip and which port this statement is issued from. Can be used to track down the user of the problematic statement.
  • db column: Displays which database the process is currently connected to.
  • Command column: Displays the executed commands of the current connection, generally sleep, query, and connect
  • time column: the duration of this state, in seconds.

The most important thing in this command is the state column. The states listed by mysql mainly include the following:

Checking table

Checking the datasheet (this is automatic).

Closing tables

The modified data in the table is being flushed to disk, and the exhausted table is being closed. This is a quick operation, if not, you should check whether the disk space is full or the disk is under heavy load.

Connect Out

The replication slave is connecting to the master.

Copying to tmp table on disk

Since the temporary result set is larger than tmp_table_size, the temporary table is being converted from memory storage to disk storage to save memory.

Creating tmp table

Creating a temporary table to hold some query results.

deleting from main table

The server is performing the first part of a multi-table delete, and the first table has just been deleted.

deleting from reference tables

The server is performing the second part of the multi-table delete, deleting records from other tables.

Flushing tables

FLUSH TABLES is being executed, waiting for other threads to close the data table.

Killed

Send a kill request to a thread, then this thread will check the kill flag, and will give up the next kill request at the same time. MySQL will check the kill flag in each main loop, but in some cases the thread may die after a short period of time. If the thread is locked by other threads, the kill request will take effect immediately when the lock is released.

Locked

locked by other queries.

Sending data

The records of the SELECT query are being processed and the results are being sent to the client.

Sorting for group

Sorting for GROUP BY.

Sorting for order

Sorting is being done for ORDER BY.

Opening tables

This process should be quick, barring other factors interfering. For example, the data table cannot be opened by other threads until the ALTER TABLE or LOCK TABLE statement is executed. Attempting to open a table.

Removing duplicates

A SELECT DISTINCT query is being executed, but MySQL cannot optimize those duplicate records in the previous stage. Therefore, MySQL needs to remove duplicate records again, and then send the results to the client.

Reopen table

A lock on a table is acquired, but the lock must be acquired after the table structure has been modified. The lock has been released, the data table is closed, and the data table is trying to be reopened.

Repair by sorting

Repair directives are sorting to create indexes.

Repair with keycache

The repair instruction is utilizing the index cache to create new indexes one by one. It will be slower than Repair by sorting.

Searching rows for update

Finding out the records that meet the conditions for updating. It must be done before UPDATE modifies the related records.

Sleeping

Waiting for the client to send a new request.

System lock

Waiting to acquire an external system lock. If you are not currently running multiple mysqld servers requesting the same table at the same time, you can disable external system locks by adding the --skip-external-locking parameter.

Upgrading lock

INSERT DELAYED is trying to acquire a lock table to insert new records.

Updating

Matching records are being searched for, and they are modified.

User Lock

Waiting for GET_LOCK().

Waiting for tables

The thread is notified that the structure of the data table has been modified, and the data table needs to be reopened to obtain the new structure. Then, in order to be able to reopen the data table, it must wait until all other threads close the table. This notification is generated in the following cases: FLUSH TABLES tbl_name, ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE, or OPTIMIZE TABLE.

Waiting for handler insert

INSERT DELAYED has processed all pending insert operations and is waiting for new requests.

View currently locked transactions

 
#查看当前正在被锁的事务(锁请求超时后则查不到)
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
+------------------+-------------+-----------+-----------+-------------+-----------------+------------+-----------+----------+----------------+
| lock_id          | lock_trx_id | lock_mode | lock_type | lock_table  | lock_index      | lock_space | lock_page | lock_rec | lock_data      |
+------------------+-------------+-----------+-----------+-------------+-----------------+------------+-----------+----------+----------------+
| 130718495:65:3:4 | 130718495   | X         | RECORD    | `test`.`bb` | GEN_CLUST_INDEX |         65 |         3 |        4 | 0x000000000300 |
| 130718496:65:3:4 | 130718496   | X         | RECORD    | `test`.`bb` | GEN_CLUST_INDEX |         65 |         3 |        4 | 0x000000000300 |
+------------------+-------------+-----------+-----------+-------------+-----------------+------------+-----------+----------+----------------+

View transactions currently waiting for locks

#查看当前等待锁的事务(锁请求超时后则查不到)
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS; 
+-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 130718499         | 130718499:65:3:4  | 130718500       | 130718500:65:3:4 |
+-------------------+-------------------+-----------------+------------------+

View currently uncommitted transactions

#查看当前未提交的事务(如果死锁等待超时,事务可能还没有关闭)
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

Mainly look at the fields pointed by the arrows. If there is blocked data (if it is not 0, it is blocked), find it and delete it according to the field in the figure below: try_mysql_thread_id as the primary key id of this data: kill id ; (Kill the process corresponding to the id). Assume that the data of try_mysql_thread_id=14 is locked. If we execute kill 14 to delete, the table will no longer be locked.

View the table being accessed

#查看正在被访问的表
mysql> show OPEN TABLES where In_use > 0;
+----------+-------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+-------+--------+-------------+
| test     | bb    |      1 |           0 |
+----------+-------+--------+-------------+

deadlock exception

#step 3:窗口一 (若第三步中锁请求太久,则出现锁超时而终止执行)
mysql> update bb set name='bbb';
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
 
 
#"窗口一" 锁请求超时前,执行第五步,使死锁产生,则该连接 "窗口二" 执行终止,"窗口一" 顺利执行
#step 5:窗口二
mysql> update aa set name='aa';
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

View the last deadlock situation

mysql> SHOW ENGINE INNODB STATUS;

Related parameter configuration

deadlock log

#死锁记录只记录最近一个死锁信息,若要将每个死锁信息都保存到错误日志,启用以下参数:
mysql> show variables like 'innodb_print_all_deadlocks';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_print_all_deadlocks | OFF   |
+----------------------------+-------+

lock wait timeout

 
#上面 【step 3:窗口一】若一直请求不到资源,默认50秒则出现锁等待超时。
mysql> show variables like 'innodb_lock_wait_timeout'; 
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
 
 
#设置全局变量 锁等待超时为60秒(新的连接生效)
#mysql> set session innodb_lock_wait_timeout=50; 
mysql> set global innodb_lock_wait_timeout=60; 

transaction rollback

 
#上面测试中,当事务中的某个语句超时只回滚该语句,事务的完整性属于被破坏了。为了回滚这个事务,启用以下参数:
mysql> show variables like 'innodb_rollback_on_timeout';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_rollback_on_timeout | OFF   |
+----------------------------+-------+

The final parameter settings are as follows: (restart service reconnection test)

[mysqld]
log-error =/var/log/mysqld3306.log
innodb_lock_wait_timeout=60     #锁请求超时时间()
innodb_rollback_on_timeout = 1  #事务中某个语句锁请求超时将回滚真个事务
innodb_print_all_deadlocks = 1  #死锁都保存到错误日志

Command=‘Sleep’

Indicates that the connection is dormant, if there are too many, you can delete it manually

#若手动删除堵塞会话,删除 Command='Sleep' 、无State、无Info、trx_weight 权重最小的。
show processlist;
SELECT trx_mysql_thread_id,trx_state,trx_started,trx_weight FROM INFORMATION_SCHEMA.INNODB_TRX;

Guess you like

Origin blog.csdn.net/qq_43842093/article/details/132009933