MySQL- long transaction Detailed

Preface: 

"Getting MySQL" series has ended, the next article I will still give priority to MySQL, the recent scene work and learning experience or their perception of the main ideas under the record, possible follow-up article is not so coherent, but still hope a lot of stand by. Closer to home, this article introduces MySQL long transaction-related content, for example, we opened a transaction has not been committed or rolled back will happen, wait for the transaction appears to be how to deal with the situation, this article will give you the answer.

Note: This article does not focus on talking about the transaction isolation level and related properties. But the introduction of long transaction-related harm and to monitor treatment. This article is based on MySQL5.7.23 version, non-repeatable read (RR) isolation level test done.

1. What is the long transaction

First of all we need to know what matters is long, as the name suggests is to run a long time, long uncommitted transactions can also be called a large transaction. Such transactions often cause a lot of congestion and lock out, is likely to cause a delay from the master, to try to avoid the use of long transaction.

Now I will show you how to turn down the transaction and long transaction simulation:

#假设我们有一张stu_tb表,结构及数据如下
mysql> show create table stu_tb\G
*************************** 1. row ***************************
       Table: stu_tb
Create Table: CREATE TABLE `stu_tb` (
  `increment_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `stu_id` int(11) NOT NULL COMMENT '学号',
  `stu_name` varchar(20) DEFAULT NULL COMMENT '学生姓名',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`increment_id`),
  UNIQUE KEY `uk_stu_id` (`stu_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='测试学生表'
1 row in set (0.01 sec)

mysql> select * from stu_tb;
+--------------+--------+----------+---------------------+---------------------+
| increment_id | stu_id | stu_name | create_time         | update_time         |
+--------------+--------+----------+---------------------+---------------------+
|            1 |   1001 | from1    | 2019-09-15 14:27:34 | 2019-09-15 14:27:34 |
|            2 |   1002 | dfsfd    | 2019-09-15 14:27:34 | 2019-09-15 14:27:34 |
|            3 |   1003 | fdgfg    | 2019-09-15 14:27:34 | 2019-09-15 14:27:34 |
|            4 |   1004 | sdfsdf   | 2019-09-15 14:27:34 | 2019-09-15 14:27:34 |
|            5 |   1005 | dsfsdg   | 2019-09-15 14:27:34 | 2019-09-15 14:27:34 |
|            6 |   1006 | fgd      | 2019-09-15 14:27:34 | 2019-09-15 14:27:34 |
|            7 |   1007 | fgds     | 2019-09-15 14:27:34 | 2019-09-15 14:27:34 |
|            8 |   1008 | dgfsa    | 2019-09-15 14:27:34 | 2019-09-15 14:27:34 |
+--------------+--------+----------+---------------------+---------------------+
8 rows in set (0.00 sec)

#显式开启事务,可用begin或start transaction
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from stu_tb where stu_id = 1006 for update;
+--------------+--------+----------+---------------------+---------------------+
| increment_id | stu_id | stu_name | create_time         | update_time         |
+--------------+--------+----------+---------------------+---------------------+
|            6 |   1006 | fgd      | 2019-09-15 14:27:34 | 2019-09-15 14:27:34 |
+--------------+--------+----------+---------------------+---------------------+
1 row in set (0.01 sec)

#如果我们不及时提交上个事务,那么这个事务就变成了长事务,当其他会话要操作这条数据时,就会一直等待。

2. How to find the long transaction

Wait for the transaction encountered a problem, we first need to do it is to find a transaction being executed. information_schema.INNODB_TRX The table includes information about the current internal affairs innodb running, this table shows the start time of the transaction, we can get a little operation to running time of the transaction.

mysql> select t.*,to_seconds(now())-to_seconds(t.trx_started) idle_time from INFORMATION_SCHEMA.INNODB_TRX t \G
*************************** 1. row ***************************
                    trx_id: 6168
                 trx_state: RUNNING
               trx_started: 2019-09-16 11:08:27
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 3
       trx_mysql_thread_id: 11
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 1
          trx_lock_structs: 3
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 2
         trx_rows_modified: 0
   trx_concurrency_tickets: 0
       trx_isolation_level: REPEATABLE READ
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
                 idle_time: 170

Idle_time is calculated in the results produced, but also the duration of the transaction. But trx_query transactions is NUL, this does not mean nothing execute the transaction, a transaction may contain multiple SQL, SQL is finished if it is no longer shows. The current transaction is being executed, innodb not know that there is no follow-up affairs sql, Shashi Hou will commit. Therefore trx_query not provide meaningful information.

If we want to see this transaction have been executed SQL, see if you can kill long transactions, how to do it? We can get together with other system tables query specific SQL queries as follows:

mysql> select now(),(UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(a.trx_started)) diff_sec,b.id,b.user,b.host,b.db,d.SQL_TEXT from information_schema.innodb_trx a inner join
    -> information_schema.PROCESSLIST b
    -> on a.TRX_MYSQL_THREAD_ID=b.id and b.command = 'Sleep'
    -> inner join performance_schema.threads c ON b.id = c.PROCESSLIST_ID
    -> inner join performance_schema.events_statements_current d ON d.THREAD_ID = c.THREAD_ID;
+---------------------+----------+----+------+-----------+--------+-----------------------------------------------------+
| now()               | diff_sec | id | user | host      | db     | SQL_TEXT                                            |
+---------------------+----------+----+------+-----------+--------+-----------------------------------------------------+
| 2019-09-16 14:06:26 |       54 | 17 | root | localhost | testdb | select * from stu_tb where stu_id = 1006 for update |
+---------------------+----------+----+------+-----------+--------+-----------------------------------------------------+

Above results diff_sec idle_time represent the same meaning as above, they are representative of the transaction number of seconds. SQL SQL_TEXT represents just executed the transaction. But then, the above statement can only be found in SQL transactions executed last, we know, may contain more than one transaction in SQL, then we would like to check this uncommitted transactions executed SQL which, whether to meet it, the answer is a combination of events_statements_history system tables also meet the demand. The following SQL statement will check out all the transactions have been executed:

mysql> SELECT
    ->   ps.id 'PROCESS ID',
    ->   ps.USER,
    ->   ps.HOST,
    ->   esh.EVENT_ID,
    ->   trx.trx_started,
    ->   esh.event_name 'EVENT NAME',
    ->   esh.sql_text 'SQL',
    ->   ps.time
    -> FROM
    ->   PERFORMANCE_SCHEMA.events_statements_history esh
    ->   JOIN PERFORMANCE_SCHEMA.threads th ON esh.thread_id = th.thread_id
    ->   JOIN information_schema.PROCESSLIST ps ON ps.id = th.processlist_id
    ->   LEFT JOIN information_schema.innodb_trx trx ON trx.trx_mysql_thread_id = ps.id
    -> WHERE
    ->   trx.trx_id IS NOT NULL
    ->   AND ps.USER != 'SYSTEM_USER'
    -> ORDER BY
    ->   esh.EVENT_ID;
+------------+------+-----------+----------+---------------------+------------------------------+-----------------------------------------------------+------+
| PROCESS ID | USER | HOST      | EVENT_ID | trx_started         | EVENT NAME                   | SQL                                                 | time |
+------------+------+-----------+----------+---------------------+------------------------------+-----------------------------------------------------+------+
|         20 | root | localhost |        1 | 2019-09-16 14:18:44 | statement/sql/select         | select @@version_comment limit 1                    |   60 |
|         20 | root | localhost |        2 | 2019-09-16 14:18:44 | statement/sql/begin          | start transaction                                   |   60 |
|         20 | root | localhost |        3 | 2019-09-16 14:18:44 | statement/sql/select         | SELECT DATABASE()                                   |   60 |
|         20 | root | localhost |        4 | 2019-09-16 14:18:44 | statement/com/Init DB        | NULL                                                |   60 |
|         20 | root | localhost |        5 | 2019-09-16 14:18:44 | statement/sql/show_databases | show databases                                      |   60 |
|         20 | root | localhost |        6 | 2019-09-16 14:18:44 | statement/sql/show_tables    | show tables                                         |   60 |
|         20 | root | localhost |        7 | 2019-09-16 14:18:44 | statement/com/Field List     | NULL                                                |   60 |
|         20 | root | localhost |        8 | 2019-09-16 14:18:44 | statement/com/Field List     | NULL                                                |   60 |
|         20 | root | localhost |        9 | 2019-09-16 14:18:44 | statement/sql/select         | select * from stu_tb                                |   60 |
|         20 | root | localhost |       10 | 2019-09-16 14:18:44 | statement/sql/select         | select * from stu_tb where stu_id = 1006 for update |   60 |
+------------+------+-----------+----------+---------------------+------------------------------+-----------------------------------------------------+------+

From these results we can see that all SQL transactions to now have been executed from the beginning, when we put the transaction-related information to check that we can determine whether the transaction can kill, so as not to affect other transactions resulting in waiting phenomenon.

In slightly expand here, long transaction can easily result in blocking or deadlock, we can usually determine the first query sys.innodb_lock_waits view there is no transaction blocking phenomenon:

#假设一个事务执行 select * from stu_tb where stu_id = 1006 for update
#另外一个事务执行 update stu_tb set stu_name = 'wang' where stu_id = 1006

mysql> select * from sys.innodb_lock_waits\G
*************************** 1. row ***************************
                wait_started: 2019-09-16 14:34:32
                    wait_age: 00:00:03
               wait_age_secs: 3
                locked_table: `testdb`.`stu_tb`
                locked_index: uk_stu_id
                 locked_type: RECORD
              waiting_trx_id: 6178
         waiting_trx_started: 2019-09-16 14:34:32
             waiting_trx_age: 00:00:03
     waiting_trx_rows_locked: 1
   waiting_trx_rows_modified: 0
                 waiting_pid: 19
               waiting_query: update stu_tb set stu_name = 'wang' where stu_id = 1006
             waiting_lock_id: 6178:47:4:7
           waiting_lock_mode: X
             blocking_trx_id: 6177
                blocking_pid: 20
              blocking_query: NULL
            blocking_lock_id: 6177:47:4:7
          blocking_lock_mode: X
        blocking_trx_started: 2019-09-16 14:18:44
            blocking_trx_age: 00:15:51
    blocking_trx_rows_locked: 2
  blocking_trx_rows_modified: 0
     sql_kill_blocking_query: KILL QUERY 20
sql_kill_blocking_connection: KILL 20

The above results show SQL and the type of lock is blocked, the more powerful is to kill the session statement gives out. But did not find the blocking session SQL execution, if we want to find out more detailed information, you can use the following statement:

mysql> SELECT
    ->   tmp.*,
    ->   c.SQL_Text blocking_sql_text,
    ->   p.HOST blocking_host
    -> FROM
    ->   (
    ->   SELECT
    ->     r.trx_state wating_trx_state,
    ->     r.trx_id waiting_trx_id,
    ->     r.trx_mysql_thread_Id waiting_thread,
    ->     r.trx_query waiting_query,
    ->     b.trx_state blocking_trx_state,
    ->     b.trx_id blocking_trx_id,
    ->     b.trx_mysql_thread_id blocking_thread,
    ->     b.trx_query blocking_query
    ->   FROM
    ->     information_schema.innodb_lock_waits w
    ->     INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
    ->     INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id
    ->   ) tmp,
    ->   information_schema.PROCESSLIST p,
    ->   PERFORMANCE_SCHEMA.events_statements_current c,
    ->   PERFORMANCE_SCHEMA.threads t
    -> WHERE
    ->   tmp.blocking_thread = p.id
    ->   AND t.thread_id = c.THREAD_ID
    ->   AND t.PROCESSLIST_ID = p.id \G
*************************** 1. row ***************************
  wating_trx_state: LOCK WAIT
    waiting_trx_id: 6180
    waiting_thread: 19
     waiting_query: update stu_tb set stu_name = 'wang' where stu_id = 1006
blocking_trx_state: RUNNING
   blocking_trx_id: 6177
   blocking_thread: 20
    blocking_query: NULL
 blocking_sql_text: select * from stu_tb where stu_id = 1006 for update
     blocking_host: localhost

The above results are even more clear, we can clearly see that the statement is blocked and the blocked end of transaction execution, help us troubleshoot and verify that you can kill blocked sessions.

3. Monitoring long transaction

We need real work under the supervision of long transaction, define a threshold value, such as execution time exceeds 30s 30s transaction is the long transaction requires recording and alarm out, to remind managers to deal with. Here are monitoring script, you can refer to, changes in the use of on demand:

#!/bin/bash
# -------------------------------------------------------------------------------
# FileName:    long_trx.sh
# Describe:    monitor long transaction
# Revision:    1.0
# Date:        2019/09/16
# Author:      wang

/usr/local/mysql/bin/mysql -N -uroot -pxxxxxx -e "select now(),(UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(a.trx_started)) diff_sec,b.id,b.user,b.host,b.db,d.SQL_TEXT from information_schema.innodb_trx a inner join
information_schema.PROCESSLIST b
on a.TRX_MYSQL_THREAD_ID=b.id and b.command = 'Sleep'
inner join performance_schema.threads c ON b.id = c.PROCESSLIST_ID
inner join performance_schema.events_statements_current d ON d.THREAD_ID = c.THREAD_ID;" | while read A B C D E F G H
do
  if [ "$C" -gt 30 ]
      then
      echo $(date +"%Y-%m-%d %H:%M:%S")
      echo "processid[$D] $E@$F in db[$G] hold transaction time $C SQL:$H"
  fi
done >> /tmp/longtransaction.txt

Briefly explain, here -gt 30 is 30 seconds, but as more than 30 seconds long transaction is identified, you can customize according to actual needs. The script added the timing of tasks to perform.

to sum up: 

This paper describes the long transaction-related content, how to find a long transaction, how long transaction processing, how to monitor the long transaction. There may be some small partners to understand the transaction is not much, I hope this article to help you. As more query transaction related statements listed in this article, are summarized as follows:

# 查询所有正在运行的事务及运行时间
select t.*,to_seconds(now())-to_seconds(t.trx_started) idle_time from INFORMATION_SCHEMA.INNODB_TRX t \G

# 查询事务详细信息及执行的SQL
select now(),(UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(a.trx_started)) diff_sec,b.id,b.user,b.host,b.db,d.SQL_TEXT from information_schema.innodb_trx a inner join information_schema.PROCESSLIST b
on a.TRX_MYSQL_THREAD_ID=b.id and b.command = 'Sleep'
inner join performance_schema.threads c ON b.id = c.PROCESSLIST_ID
inner join performance_schema.events_statements_current d ON d.THREAD_ID = c.THREAD_ID;

# 查询事务执行过的所有历史SQL记录
SELECT
  ps.id 'PROCESS ID',
  ps.USER,
  ps.HOST,
  esh.EVENT_ID,
  trx.trx_started,
  esh.event_name 'EVENT NAME',
  esh.sql_text 'SQL',
  ps.time 
FROM
  PERFORMANCE_SCHEMA.events_statements_history esh
  JOIN PERFORMANCE_SCHEMA.threads th ON esh.thread_id = th.thread_id
  JOIN information_schema.PROCESSLIST ps ON ps.id = th.processlist_id
  LEFT JOIN information_schema.innodb_trx trx ON trx.trx_mysql_thread_id = ps.id 
WHERE
  trx.trx_id IS NOT NULL 
  AND ps.USER != 'SYSTEM_USER' 
ORDER BY
  esh.EVENT_ID;

 # 简单查询事务锁
 select * from sys.innodb_lock_waits\G

 # 查询事务锁详细信息
 SELECT
  tmp.*,
  c.SQL_Text blocking_sql_text,
  p.HOST blocking_host 
FROM
  (
  SELECT
    r.trx_state wating_trx_state,
    r.trx_id waiting_trx_id,
    r.trx_mysql_thread_Id waiting_thread,
    r.trx_query waiting_query,
    b.trx_state blocking_trx_state,
    b.trx_id blocking_trx_id,
    b.trx_mysql_thread_id blocking_thread,
    b.trx_query blocking_query 
  FROM
    information_schema.innodb_lock_waits w
    INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
    INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id 
  ) tmp,
  information_schema.PROCESSLIST p,
  PERFORMANCE_SCHEMA.events_statements_current c,
  PERFORMANCE_SCHEMA.threads t 
WHERE
  tmp.blocking_thread = p.id 
  AND t.thread_id = c.THREAD_ID 
  AND t.PROCESSLIST_ID = p.id \G

Guess you like

Origin blog.51cto.com/10814168/2440043