mysql未提交事务sql_MySQL如何找出未提交事务的SQL浅析
--準備測試環境數據(實驗環境為MySQL 8.0.18社區版)mysql>?create?table?kkk(id?int?,?name?varchar(12));Query?OK,?0?rows?affected?(0.34?sec)mysql>?insert?into?kkk?values(1,?'kerry');Query?OK,?1?row?affected?(0.01?sec)mysql>?insert?into?kkk?values(2,?'jerry');Query?OK,?1?row?affected?(0.00?sec)mysql>?insert?into?kkk?values(3,?'ken');Query?OK,?1?row?affected?(0.00?sec)mysql>?mysql>?create?table?t(a???varchar(10));Query?OK,?0?rows?affected?(0.47?sec)mysql>?insert?into?t?values('test');Query?OK,?1?row?affected?(0.00?sec)
在一個會話窗口(連接ID=38)執行下面SQLmysql>?select?connection_id()?from?dual;+-----------------+|?connection_id()?|+-----------------+|??????????????38?|+-----------------+1?row?in?set?(0.00?sec)mysql>?set?session?autocommit=0;Query?OK,?0?rows?affected?(0.00?sec)mysql>??delete?from?kkk?where?id?=1;Query?OK,?1?row?affected?(0.00?sec)mysql>
在另外一個會話窗口(連接ID=39)執行下面SQLmysql>?SELECT?t.trx_mysql_thread_id
->???,t.trx_id
->???????,t.trx_state
->???????,t.trx_tables_in_use
->???????,t.trx_tables_locked
->???????,t.trx_query
->???????,t.trx_rows_locked
->???????,t.trx_rows_modified
->???????,t.trx_lock_structs
->???????,t.trx_started
->???????,t.trx_isolation_level
->???????,p.time
->???????,p.user
->???????,p.host
->???????,p.db
->???????,p.command
->?FROM???information_schema.innodb_trx?t
->????????INNER?JOIN?information_schema.processlist?p
->????????????????ON?t.trx_mysql_thread_id?=?p.id
->?WHERE??t.trx_state?=?'RUNNING'
->????????AND?p.time?>?4
->????????AND?p.command?=?'Sleep'\G?***************************?1.?row?***************************trx_mysql_thread_id:?38
trx_id:?7981581
trx_state:?RUNNING
trx_tables_in_use:?0
trx_tables_locked:?1
trx_query:?NULL
trx_rows_locked:?4
trx_rows_modified:?1
trx_lock_structs:?2
trx_started:?2020-12-03?15:39:37trx_isolation_level:?REPEATABLE?READ
time:?23
user:?root
host:?localhost
db:?MyDB
command:?Sleep1?row?in?set?(0.00?sec)
雖然上面這個SQL找不出事務執行過的SQL,其實呢,MySQL中未提交事務的最后執行的一個SQL是可以通過下面腳本準確查找出來的。如下所示:SELECT?t.trx_mysql_thread_id????????????????????????AS?connection_id
,t.trx_id?????????????????????????????????????AS?trx_id
,t.trx_state??????????????????????????????????AS?trx_state
,t.trx_started????????????????????????????????AS?trx_started
,TIMESTAMPDIFF(SECOND,t.trx_started,?now())???AS?"trx_run_time(s)"
,t.trx_requested_lock_id??????????????????????AS?trx_requested_lock_id
,t.trx_operation_state????????????????????????AS?trx_operation_state
,t.trx_tables_in_use??????????????????????????AS?trx_tables_in_use
,t.trx_tables_locked??????????????????????????AS?trx_tables_locked
,t.trx_rows_locked????????????????????????????AS?trx_rows_locked
,t.trx_isolation_level????????????????????????AS?trx_isolation_level
,t.trx_is_read_only???????????????????????????AS?trx_is_read_only
,t.trx_autocommit_non_locking?????????????????AS?trx_autocommit_non_locking
,e.event_name?????????????????????????????????AS?event_name
,e.timer_wait?/?1000000000000?????????????????AS?timer_wait
,e.sql_text?FROM???information_schema.innodb_trx?t,
performance_schema.events_statements_current?e,
performance_schema.threads?c?WHERE??t.trx_mysql_thread_id?=?c.processlist_id
AND?e.thread_id?=?c.thread_id\G;
如下截圖所示:
在會話窗口(連接ID=38)繼續執行下面SQL:"select * from t;"。 如下所示mysql>?set?session?autocommit=0;Query?OK,?0?rows?affected?(0.01?sec)mysql>?delete?from?kkk?where?id?=1;Query?OK,?1?row?affected?(0.00?sec)mysql>?select?*?from?t;+------+|?a????|+------+|?test?|+------+1?row?in?set?(0.00?sec)mysql>
在會話窗口(連接ID=39)上繼續執行下面SQL,你會發現捕獲的是事務最后執行的SQL語句“select * from t”mysql>?SELECT?t.trx_mysql_thread_id????????????????????????AS?connection_id
->???????,t.trx_id?????????????????????????????????????AS?trx_id
->???????,t.trx_state??????????????????????????????????AS?trx_state
->???????,t.trx_started????????????????????????????????AS?trx_started
->???????,TIMESTAMPDIFF(SECOND,t.trx_started,?now())???AS?"trx_run_time(s)"
->???????,t.trx_requested_lock_id??????????????????????AS?trx_requested_lock_id
->???????,t.trx_operation_state????????????????????????AS?trx_operation_state
->???????,t.trx_tables_in_use??????????????????????????AS?trx_tables_in_use
->???????,t.trx_tables_locked??????????????????????????AS?trx_tables_locked
->???????,t.trx_rows_locked????????????????????????????AS?trx_rows_locked
->???????,t.trx_isolation_level????????????????????????AS?trx_isolation_level
->???????,t.trx_is_read_only???????????????????????????AS?trx_is_read_only
->???????,t.trx_autocommit_non_locking?????????????????AS?trx_autocommit_non_locking
->???????,e.event_name?????????????????????????????????AS?event_name
->???????,e.timer_wait?/?1000000000000?????????????????AS?timer_wait
->???????,e.sql_text
->?FROM???information_schema.innodb_trx?t,
->????????performance_schema.events_statements_current?e,
->????????performance_schema.threads?c
->?WHERE??t.trx_mysql_thread_id?=?c.processlist_id
->????AND?e.thread_id?=?c.thread_id\G;?***************************?1.?row?***************************
connection_id:?38
trx_id:?7981581
trx_state:?RUNNING
trx_started:?2020-12-03?15:39:37
trx_run_time(s):?237
trx_requested_lock_id:?NULL
trx_operation_state:?NULL
trx_tables_in_use:?0
trx_tables_locked:?1
trx_rows_locked:?4
trx_isolation_level:?REPEATABLE?READ
trx_is_read_only:?0trx_autocommit_non_locking:?0
event_name:?statement/sql/select
timer_wait:?0.0002
sql_text:?select?*?from?t1?row?in?set?(0.00?sec)ERROR:?No?query?specified
也是說上面SQL只能獲取未提交事務最后執行的一個SQL語句,生產環境中,一個事務中往往不止一個SQL語句,而是多個SQL語句的集合。如果想將一個未提交事務里面所有執行過的SQL找出來怎么辦呢? 其實在MySQL中還是有辦法的。下面SQL語句就可以找出SELECT?ps.id
,trx.trx_id
,trx_started
,trx_state
,trx_isolation_level
,esh.event_id
,esh.timer_wait
,esh.event_name
,esh.sql_text
,esh.returned_sqlstate
,esh.mysql_errno
,esh.message_text
,esh.errors
,esh.warnings?FROM???information_schema.innodb_trx?trx
JOIN?information_schema.processlist?ps
ON?trx.trx_mysql_thread_id?=?ps.id
LEFT?JOIN?performance_schema.threads?th
ON?th.processlist_id?=?trx.trx_mysql_thread_id
LEFT?JOIN?performance_schema.events_statements_history?esh
ON?esh.thread_id?=?th.thread_id?WHERE??trx.trx_started?
AND?ps.user?!=?'SYSTEM_USER'???ORDER??BY?esh.event_id;
或者SELECT?trx.trx_mysql_thread_id?AS?processlist_id
,sc.thread_id
,trx.trx_started
,TO_SECONDS(now())-TO_SECONDS(trx_started)?AS?trx_last_time
,pc1.user
,pc1.host
,pc1.db
,sc.SQL_TEXT?AS?current_sql_text
,sh.history_sql_testFROM?INFORMATION_SCHEMA.INNODB_TRX?trxINNER?JOIN?INFORMATION_SCHEMA.processlist?pc1?ON?trx.trx_mysql_thread_id=pc1.idINNER?JOIN?performance_schema.threads?th?on?th.processlist_id?=?trx.trx_mysql_thread_idINNER?JOIN?performance_schema.events_statements_current?sc?ON?sc.THREAD_ID?=?th.THREAD_IDINNER?JOIN?(
SELECT?thread_id?AS?thread_id,?GROUP_CONCAT(SQL_TEXT?SEPARATOR?';')?AS?history_sql_test
FROM?performance_schema.events_statements_history
GROUP?BY?thread_id
)?sh?ON?sh.thread_id?=?th.thread_idWHERE?trx_mysql_thread_id?!=?connection_id()
AND?TO_SECONDS(now())-TO_SECONDS(trx_started)?>=?0?;
但是這兩個SQL有個問題:它會找出當前連接歷史上所有執行過的SQL(當然前提是這些SQL還保存在performance_schema.events_statements_history表中),也就是說這個SQL,不僅查詢出未提交事務所有執行過的腳本,而且會查詢出很多歷史SQL腳本,例如,這個會話(連接)之前事務的SQL語句,而且還有一個比較頭疼的問題:這里不好區分哪些SQL對應哪些事務。需要借助其他信息來甄別。比較費時費力。如下截圖所示。
因為只有information_schema.innodb_trx系統表中包含事務的開始時間(trx_started),其它系統表沒有跟事務相關的時間,只能借助performance_schema.events_statements_history中的TIMER_START字段來獲取事件的SQL開始執行的時間,而這個時間必然是小于或等于對應事務的開始時間(trx_started)的。所以從這個突破口來找出未提交事務的所有SQL,下面是關于TIMER_START等字段的詳細介紹。關于TIMER_START,TIMER_END,TIMER_WAIT的介紹如下:
TIMER_START,TIMER_END,TIMER_WAIT:事件的時間信息。這些值的單位是皮秒(萬億分之一秒)。
TIMER_START和TIMER_END值表示事件的開始時間和結束時間。 TIMER_WAIT是事件執行消耗的時間(持續時間)
如果事件未執行完成,則TIMER_END為當前時間,TIMER_WAIT為當前為止所經過的時間(TIMER_END -
TIMER_START)。 如果監視儀器配置表setup_instruments中對應的監視器TIMED字段被設置為
NO,則不會收集該監視器的時間信息,那么對于該事件采集的信息記錄中,TIMER_START,TIMER_END和TIMER_WAIT字段值均為NULL
測試、折騰了好久,終于搞出了一個幾乎完美的SQL:SELECT?@dt_ts:=UNIX_TIMESTAMP(NOW());SELECT
@dt_timer:=MAX(sh.TIMER_START)FROM?performance_schema.threads?AS?tINNER?JOIN?performance_schema.events_statements_history?AS?shON?t.`THREAD_ID`=sh.`THREAD_ID`WHERE?t.PROCESSLIST_ID=CONNECTION_ID();
SELECT?sh.current_schema???AS?database_name
,t.thread_id
,it.trx_mysql_thread_id?????????AS?connection_id
,it.trx_id
,sh.event_id
,it.trx_state
,REPLACE(REPLACE(REPLACE(sh.`SQL_TEXT`,'\n','?'),'\r','?'),'\t','?')?AS?executed_sql
,it.trx_started
,FROM_UNIXTIME(@dt_ts-CAST((@dt_timer-sh.TIMER_START)/1000000000000??AS?SIGNED))?AS?start_time
,FROM_UNIXTIME(@dt_ts-CAST((@dt_timer-sh.TIMER_END)??/1000000000000??AS?SIGNED))?AS?end_time
,(sh.TIMER_END-sh.TIMER_START)/1000000000000?AS?used_seconds
,sh.TIMER_WAIT/1000000000000?AS?wait_seconds
,sh.LOCK_TIME/1000000000000?AS?lock_seconds
,sh.ROWS_AFFECTED?AS?affected_rows
,sh.ROWS_SENT?AS?send_rowsFROM?performance_schema.threads?AS?tINNER?JOIN?information_schema.innodb_trx?it?ON??it.trx_mysql_thread_id?=?t.processlist_id?INNER?JOIN?performance_schema.events_statements_history?AS?sh
ON?t.`THREAD_ID`=sh.`THREAD_ID`WHERE?t.PROCESSLIST_ID?IN?(
SELECT
p.ID?AS?conn_id
FROM?`information_schema`.`INNODB_TRX`?t
INNER?JOIN?`information_schema`.`PROCESSLIST`?p
ON?t.trx_mysql_thread_id=p.id
WHERE?t.trx_state='RUNNING'
AND?p.COMMAND='Sleep'
AND?p.TIME>2
)AND?sh.TIMER_START=it.trx_startedORDER?BY?it.trx_id?ASC,?sh.TIMER_START?ASC;
它能找出未提交事務的SQL,簡單測試完全沒有問題,同時構造幾個未提交事務測試也OK。但是上面SQL由三個SQL組成,總感覺有點別扭,研究了一下,可以改造成下面SQL。SELECT?sh.current_schema???AS?database_name
,t.thread_id
,it.trx_mysql_thread_id?????????AS?connection_id
,it.trx_id
,sh.event_id
,it.trx_state
,REPLACE(REPLACE(REPLACE(sh.`SQL_TEXT`,'\n','?'),'\r','?'),'\t','?')?AS?executed_sql
,it.trx_started
,DATE_SUB(NOW(),?INTERVAL?(SELECT?VARIABLE_VALUE?FROM?performance_schema.global_status?WHERE?VARIABLE_NAME='UPTIME')?-?sh.TIMER_START*10e-13?second)?AS?start_time
,DATE_SUB(NOW(),?INTERVAL?(SELECT?VARIABLE_VALUE?FROM?performance_schema.global_status?WHERE?VARIABLE_NAME='UPTIME')?-?sh.TIMER_END*10e-13?second)???AS?end_time
,(sh.TIMER_END-sh.TIMER_START)/1000000000000?AS?used_seconds
,sh.TIMER_WAIT/1000000000000?AS?wait_seconds
,sh.LOCK_TIME/1000000000000?AS?lock_seconds
,sh.ROWS_AFFECTED?AS?affected_rows
,sh.ROWS_SENT?AS?send_rowsFROM?performance_schema.threads?AS?tINNER?JOIN?information_schema.innodb_trx?it?ON??it.trx_mysql_thread_id?=?t.processlist_id?INNER?JOIN?performance_schema.events_statements_history?AS?sh
ON?t.`THREAD_ID`=sh.`THREAD_ID`WHERE?t.PROCESSLIST_ID?IN?(
SELECT
p.ID?AS?conn_id
FROM?`information_schema`.`INNODB_TRX`?t
INNER?JOIN?`information_schema`.`PROCESSLIST`?p
ON?t.trx_mysql_thread_id=p.id
WHERE?t.trx_state='RUNNING'
AND?p.COMMAND='Sleep'
AND?p.TIME>2
)AND?sh.TIMER_START=it.trx_startedORDER?BY?it.trx_id?ASC,?sh.TIMER_START?ASC;注意:performance_schema.global_status是MySQL
5.7引入的,如果數據庫是MySQL
5.6的話,可以用INFORMATION_SCHEMA.GLOBAL_STATUS替換SQL中的performance_schema.global_status
那么是否這個SQL就一定完美了呢?
網友MSSQL123反饋在一個測試環境中,發現上面這種SQL居然查不出任何數據,因為FROM_UNIXTIME(@dt_ts-CAST((@dt_timer-sh.TIMER_START)/1000000000000
AS SIGNED))
>=it.trx_started會將數據過濾掉,檢查發現trx_started都大于start_time.那么為什么出現這種情況呢?
Modifications
to the setup_timers table affect monitoring immediately. Events already
in progress may use the original timer for the begin time and the new
timer for the end time. To avoid unpredictable results after you make
timer changes, use TRUNCATE TABLE to reset Performance Schema
statistics.
The timer baseline (“time zero”) occurs at Performance
Schema initialization during server startup. TIMER_START and TIMER_END
values in events represent picoseconds since the baseline. TIMER_WAIT
values are durations in picoseconds.
Picosecond values in events are
approximate. Their accuracy is subject to the usual forms of error
associated with conversion from one unit to another. If the CYCLE timer
is used and the processor rate varies, there might be drift. For these
reasons, it is not reasonable to look at the TIMER_START value for an
event as an accurate measure of time elapsed since server startup. On
the other hand, it is reasonable to use TIMER_START or TIMER_WAIT values
in ORDER BY clauses to order events by start time or duration.
The
choice of picoseconds in events rather than a value such as microseconds
has a performance basis. One implementation goal was to show results in
a uniform time unit, regardless of the timer. In an ideal world this
time unit would look like a wall-clock unit and be reasonably precise;
in other words, microseconds. But to convert cycles or nanoseconds to
microseconds, it would be necessary to perform a division for every
instrumentation. Division is expensive on many platforms. Multiplication
is not expensive, so that is what is used. Therefore, the time unit is
an integer multiple of the highest possible TIMER_FREQUENCY value, using
a multiplier large enough to ensure that there is no major precision
loss. The result is that the time unit is “picoseconds.” This precision
is spurious, but the decision enables overhead to be minimized.
Before
MySQL 5.7.8, while a wait, stage, statement, or transaction event is
executing, the respective current-event tables display the event with
TIMER_START populated, but with TIMER_END and TIMER_WAIT set to NULL
其中一段內容翻譯如下:事件中的皮秒值是近似值。它們的準確性受與從一個單位轉換到另一單位相關的常見誤差形式的影響。如果使用了CYCLE定時器,并且處理器速率有所變化,則可能會有偏差。由于這些原因,將事件的TIMER_START值視為自服務器啟動以來經過的時間的準確度量是不合理的。另一方面,在ORDER
BY子句中使用TIMER_START或TIMER_WAIT值來按開始時間或持續時間對事件進行排序是合理的。
我們往往想一擊必殺的解決問題,但是由于復雜的環境和一些不可控因素,現實往往就是“沒有銀彈”這么殘酷。如果遇到TIMER_START的波動或偏差影響查詢結果,那么我必須通過文字前面的SQL找出大量SQL,然后通過其它字段或信息人工甄別哪些才是未提交事務的SQL。原作者:瀟湘隱者
原文鏈接:MySQL如何找出未提交事務的SQL淺析
原出處:DBA閑思雜想錄
侵刪
總結
以上是生活随笔為你收集整理的mysql未提交事务sql_MySQL如何找出未提交事务的SQL浅析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql pl安装教程_ubuntu
- 下一篇: 二房东不给退押金怎么办?