在MySQL中显示当前活动的事务

3
有没有办法让MySQL告诉我当前是否在事务中?我正在使用命令行mysql客户端进行交互式会话,已经打开和关闭了几个事务,现在我不应该处于事务中,但它似乎仍然是。那么如何检查/验证我的连接状态?我尝试了一下运气,键入了SHOW TRANSACTION,但并没有这样的东西。
尽职调查:
我查看了其他问题(当然还有事务文档),但没有找到答案。这个问题是关于在连接断开后恢复事务的。这个问题似乎是在问其他线程中是否有活动事务。我想知道的是我的连接是否在事务中。
我也尝试了SELECT @@AUTOCOMMIT FROM DUAL,就像这里建议的那样。但是它没有帮助:当我开始一个事务时,它的值不会改变,仍然是1(“自动提交已启用”)。
3个回答

8

information_schema.innodb_trx 可以告诉你是否在 InnoDB 中进行事务。但是要注意,如果您还没有访问任何表或明确创建读取快照,则仅在 MySQL(即“服务器层”)中进行事务,而不在 InnoDB(即“存储引擎层”)中。

mysql> SELECT count(1) FROM information_schema.innodb_trx 
    -> WHERE trx_mysql_thread_id = CONNECTION_ID();
+----------+
| count(1) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

好的,以前没有,但是现在我有一笔交易,然后...

mysql> SELECT count(1) FROM information_schema.innodb_trx 
    -> WHERE trx_mysql_thread_id = CONNECTION_ID();
+----------+
| count(1) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)

在innodb_trx中当前的CONNECTION_ID()仍未有任何内容。

但是,如果我向InnoDB表写入或者只是读取数据...

mysql> SELECT COUNT(1) FROM t1;
+----------+
| COUNT(1) |
+----------+
|      301 |
+----------+
1 row in set (0.00 sec)

现在,我可以看到我的交易记录,因为InnoDB已经意识到它。

mysql> SELECT count(1) FROM information_schema.innodb_trx 
    -> WHERE trx_mysql_thread_id = CONNECTION_ID();
+----------+
| count(1) |
+----------+
|        1 |
+----------+
1 row in set (0.00 sec)

mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)

让我们验证它是否已经消失了...

mysql> SELECT count(1) FROM information_schema.innodb_trx 
    -> WHERE trx_mysql_thread_id = CONNECTION_ID();
+----------+
| count(1) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)

现在,请告诉服务器让存储引擎知道我的MVCC视图从现在开始,而不是以后:
mysql> START TRANSACTION WITH CONSISTENT SNAPSHOT;
Query OK, 0 rows affected (0.00 sec)

请注意,除非我的隔离级别允许,否则这实际上不会给我一个“一致的”快照。但是,这已经足够让InnoDB知道我在这里。
mysql> SELECT count(1) FROM information_schema.innodb_trx 
    -> WHERE trx_mysql_thread_id = CONNECTION_ID();
+----------+
| count(1) |
+----------+
|        1 |
+----------+
1 row in set (0.00 sec)

...而InnoDB会立即了解该事务。


现在,还有另一种方法来确定你当前是否正在进行事务。或者更正确地说,应该是有另一种方法来确定你当前没有在进行事务。

我将其用于必须在事务中运行的存储过程--调用方负责启动和提交或回滚,如果没有活动事务,该过程将拒绝运行。如何实现呢?

该过程调用另一个过程,如果我有事务,则默默成功,但如果没有,则抛出异常。当一个过程调用第二个过程,并且第二个过程抛出异常时,除非第一个过程安装了HANDLER来捕获错误,否则第一个过程将以相同的异常终止。

因此,当我的外部过程调用此过程时,如果存在活动事务,则不会发生任何事情,外部过程将被允许运行:

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> CALL mysql.require_transaction;
Query OK, 0 rows affected (0.00 sec)

在我的存储过程的开头,我需要做的是只有在被事务调用时才运行。

没有错误,我们正在进行一个事务。如果另一个存储过程调用它,那么该存储过程将仅继续执行下一条指令。

但是,如果我们调用我的 require_transaction 存储过程而我们不在一个事务中:

mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)

mysql> CALL mysql.require_transaction;
ERROR 1644 (42000): you must have an active database transaction before attempting
this operation

很好。我们如何使用自定义错误消息使调用者崩溃?
DELIMITER $$
CREATE PROCEDURE `mysql`.`require_transaction`()
BEGIN

-- test the session's transactional status, 
-- throwing an exception if we aren't in a transaction,
-- but finishing successfully if we are

DECLARE CONTINUE HANDLER 
        FOR 1305 
        SIGNAL SQLSTATE '42000' 
        SET MESSAGE_TEXT = 'you must have an active database transaction before attempting this operation';

SAVEPOINT `we created to be sure you were in a transaction`;
ROLLBACK TO SAVEPOINT `we created to be sure you were in a transaction`;

END $$
DELIMITER ;

这是我长期以来的解决方法,因为我认为这是MySQL设计中的一个重大疏忽 - 从SQL界面无法确定当前是否在事务中。以下是它的工作原理:
  • 创建一个 SAVEPOINT 并立即回滚到它实际上是一个空操作。只要没有已经使用相同名称的活动保存点,就不会有任何影响。我使用了高度不可能的名称 we created to be sure you were in a transaction 来命名我的 SAVEPOINT
  • 如果您不在事务中,则无法创建 SAVEPOINT,但这实际上会静默失败。
  • 回滚到不存在的 SAVEPOINT 将抛出错误1305,因此如果您不在事务中,则未创建,现在也不存在,这就是您的错误。如果您在事务中,则创建并释放了 SAVEPOINT,使您的事务保持不变。
mysql> ROLLBACK TO SAVEPOINT `we created to be sure you were in a transaction`;
ERROR 1305 (42000): SAVEPOINT we created to be sure you were in a transaction does not exist
mysql>

这是一个很妙的技巧。我使用“不存在”作为我的虚假保存点的名称,将其附加到对象名称后以形成错误消息。

在MySQL 5.1上没有SIGNAL,所以我的require_transaction存储过程只是以本机错误终止,这几乎是有意义的……或者至少足够有意义,以至于有人会向DBA(我)询问它的含义。

为了使其更美观,在MySQL Server 5.5及以上版本中,我们使用CONTINUE HANDLER捕获错误1305,允许我们使用SIGNAL设置自定义错误消息。

设置并立即回滚到保存点是一种悲惨但可靠的确定是否在事务中的方式。


1
谢谢!SQL并不适合交互使用或人为错误(实际上差不多是一回事),对吗?由于我的使用情况是交互式控制台,"尝试创建一个SAVEPOINT并回滚到它" 对我的需求来说是一个相当好的解决方案。(也许你应该将其作为一个TL;DNR简介放在顶部 :-)) - undefined
我会考虑采纳那个建议,并且会对答案进行一些重构。谢谢。 :) - undefined
1
我被困在一个没有innodb_trx的遗留系统中,因此savepoint/rollback hack是救命稻草!你是个聪明的人!!! - undefined
@Michael-sqlbot,事实证明遗留系统也没有SIGNAL,所以我正在尝试编写一个存储函数,返回一个TINYINT(布尔值),用于指示我是否处于事务中,但它总是返回1 :-( - undefined

1

我刚刚尝试了一下,开始事务之前和之后都返回了0 - undefined
值得注意的是:它需要“PROCESS”权限,因此并不总是一个选择(即使你可以修复它)。 - undefined
看起来应该可以工作,不过很奇怪。我检查了innodb_trx表,在我调用START TRANSACTION之后它仍然完全为空。(虽然我没有对数据库进行任何修改。) - undefined

0

如果像我一样,被诸神诅咒而要处理一个如此古老的遗留系统,它没有innodb_trxSIGNAL,那么下面对Michael-sqlbot的过程进行的改编可能适合你(注意:由于某种原因,它不能是一个函数,必须是一个过程)。

SET @saved_cs_client      = @@character_set_client;
SET @saved_cs_results     = @@character_set_results;
SET @saved_col_connection = @@collation_connection;
SET character_set_client  = utf8;
SET character_set_results = utf8;
SET collation_connection  = utf8_general_ci;
SET @saved_sql_mode       = @@sql_mode;
SET sql_mode              = 'STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ALLOW_INVALID_DATES,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER';

DROP PROCEDURE IF EXISTS `sp_is_in_transaction`;
DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_is_in_transaction`(
OUT is_in_transaction TINYINT
)
BEGIN

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION # 1305
    BEGIN
        SET is_in_transaction = 0 ; # on error realize we are NOT in a transaction
    END;

SET is_in_transaction = 1 ;
SAVEPOINT `savepoint_sp_is_in_transaction`;
ROLLBACK TO SAVEPOINT `savepoint_sp_is_in_transaction`;

END $$
DELIMITER ;

SET sql_mode              = @saved_sql_mode;
SET character_set_client  = @saved_cs_client;
SET character_set_results = @saved_cs_results;
SET collation_connection  = @saved_col_connection;

测试区块

call sp_is_in_transaction( @in_trans );
select @in_trans ;

start transaction ;
call sp_is_in_transaction( @in_trans );
select @in_trans ;
rollback ;

call sp_is_in_transaction( @in_trans );
select @in_trans ;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接