技术分享 | MySQL 存储过程中的只读语句超时怎么办?

本文探讨了MySQL中max_execution_time参数对单独select语句的限制,以及在存储过程中的失效情况。作者介绍了如何通过脚本和Percona-toolkit的pt-kill工具来管理超时,同时提醒读者避免在生产环境中使用这类自动取消功能。

作者:杨涛涛

资深数据库专家,专研 MySQL 十余年。擅长 MySQL、PostgreSQL、MongoDB 等开源数据库相关的备份恢复、SQL 调优、监控运维、高可用架构设计等。目前任职于爱可生,为各大运营商及银行金融企业提供 MySQL 相关技术支持、MySQL 相关课程培训等工作。

本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


MySQL 有一个参数叫 max_execution_time ,用来设置只读语句执行的超时时间,但是仅对单独执行的 select 语句有效;对于非单独执行的 select 语句,比如包含在存储过程、触发器等内置事务块里则不生效。官方手册上对这个参数解释如下:

max_execution_time applies as follows:

  • The global max_execution_time value provides the default for the session value for new connections. The session value applies to SELECT executions executed within the session that include no MAX_EXECUTION_TIME(*N*) optimizer hint or for which N is 0.
  • max_execution_time applies to read-only SELECT statements. Statements that are not read only are those that invoke a stored function that modifies data as a side effect.
  • max_execution_time is ignored for SELECT statements in stored programs.

那对这种非单独出现的 select 语句,该如何控制超时时间呢?

先来看下参数 max_execution_time 设置后的效果。此参数设置后,select 语句如果执行时间过长,会直接被 cancel 掉,并且报错,如下所示:

mysql> set @@max_execution_time=1000;
Query OK, 0 rows affected (0.00 sec)

mysql> select sleep(2) from t1 limit 1;
ERROR 3024 (HY000): Query execution was interrupted, maximum statement execution time exceeded

或者是采用直接加 Hint 的方式,也能限制 select 语句的执行时间: 下面两种方式都能起到限制 select 语句执行时间的作用。

mysql> select /*+ max_execution_time(1000) */ sleep(2) from t1 limit 2;
ERROR 3024 (HY000): Query execution was interrupted, maximum statement execution time exceeded

mysql> select /*+ set_var(max_execution_time=1000) */ sleep(2) from t1 limit 2;
ERROR 3024 (HY000): Query execution was interrupted, maximum statement execution time exceeded

那如果把这条 select 语句封装在存储过程内部,按照手册上对参数 max_execution_time 的解释,则不生效。比如新建一个存储过程 sp_test :

DELIMITER $$

USE `ytt`$$

DROP PROCEDURE IF EXISTS `sp_test`$$

CREATE DEFINER=`admin`@`%` PROCEDURE `sp_test`()
BEGIN
      select sleep(2) from t1 limit 1;
    END$$

DELIMITER ;

重新设置 max_execution_time 值为1秒:调用存储过程 sp_test , 可以正常执行,select 语句并没有被 cancel 掉!

mysql> call sp_test;
+----------+
| sleep(2) |
+----------+
|        0 |
+----------+
1 rows in set (2.01 sec)

Query OK, 0 rows affected (2.01 sec)

那如何解决这个问题呢?

为了更方便大家测试,把语句 select sleep(2) from t1 limit 1 改为 select sleep(2000) from t1 limit 1 。既然 MySQL 层面有这样的限制,那只能从非 MySQL 层面来想办法。最直接有效的就是写个脚本来主动 cancel 掉 select 语句。脚本如下:

root@ytt-normal:/home/ytt/script# cat kill_query 
#!/bin/sh
QUERY_ID=`mysql -ss -e "select id from information_schema.processlist where user='admin' and db='ytt' and time>10 and regexp_like(info,'^select','i')"`
if [ $QUERY_ID ];then
	echo "kill query $QUERY_ID"
	mysql -e "kill query $QUERY_ID"
fi

完后把脚本放到 crontab 或者 MySQL 自己的 event 里来定时执行即可。单独执行脚本效果如下:

root@ytt-normal:/home/ytt/script# ./kill_query 
kill query 50

除了自己编写脚本,还有一个工具可以实现类似的效果,它包含在我们熟知的 Percona-toolkit 工具箱里,叫 pt-kill 。

pt-kill 工具可以根据各种触发条件来执行指定动作:比如 cancel 掉指定 SQL 语句、kill 掉指定 session 等。所以完全可以使用 pt-kill 工具来实现 select 语句超时被自动 cancel 掉。如下所示:pt-kill 工具会在后台一直运行,监听 MySQL 进程,一旦触发条件被激活,即可执行相应动作。

root@ytt-normal:/home/ytt/script# pt-kill --match-db=ytt --match-user=admin --match-host=%  \--match-info='^select' --victims=all --busy-time='10s' --print --kill-query

# 2022-08-15T17:29:03 KILL QUERY 50 (Query 11 sec) select sleep(2000) from t1 limit 1

有一点需要注意:select 语句超时自动 cancel 掉这样的功能不适宜用在生产环境!因为你无法预知其执行结果的时效性、上下文是否相关等特点。

<think>嗯,用户想了解在Qt中使用MySQL,并通过SQL语句操作后将数据存放到QSqlQueryModel上。首先,我需要回忆一下Qt中数据库操作的基本流程。 首先,Qt连接数据库需要用到QSqlDatabase类,记得要添加SQL模块,可能需要在.pro文件中加入QT += sql。然后,加载MySQL驱动,不过Qt默认可能没有MySQL的插件,可能需要用户自己编译或者确保环境变量正确。这点可能需要提醒用户注意。 接下来,建立数据库连接,设置主机名、数据库名、用户名和密码。如果连接失败,应该检查错误信息,比如调用lastError()方法。这一步很重要,因为连接失败的话后续操作都无法进行。 然后,执行SQL语句。用户提到了使用QSqlQuery来执行,比如创建表、插入数据、查询等。需要注意的是,执行查询后要检查是否有错误,可以用exec()的返回值或者lastError()来确认。 之后,数据存放到QSqlQueryModel上。QSqlQueryModel是一个用于只读数据的模型,可以直接设置查询语句,然后和视图如QTableView绑定显示。但用户可能想通过执行SQL语句后手动设置模型的数据,可能需要使用setQuery方法,或者考虑使用QSqlTableModel,不过用户明确提到QSqlQueryModel,所以应该以这个为主。 另外,QSqlQueryModel的数据一旦设置后是只读的,如果用户需要编辑,可能需要用QSqlTableModel或者自定义模型。这点可能需要提到,但用户的问题可能暂时不需要编辑功能。 可能还需要注意,执行完INSERT、UPDATE等操作后,是否需要刷新模型的数据,比如重新执行查询。例如,插入数据后,再次调用model->setQuery("SELECT * FROM table"),但要注意这样可能会清除之前的模型数据,可能需要重新设置视图的模型。 此外,记得在代码中包含必要的头文件,比如QSqlDatabase、QSqlQuery、QSqlQueryModel、QTableView等。还有,打开数据库后,最后要记得关闭连接。 用户可能遇到的问题包括驱动未加载,比如出现“QMYSQL driver not loaded”的错误,这时候需要确保Qt的MySQL插件正确安装,并且将libmysql.dll放在正确的位置。这个可能需要根据不同的操作系统进行说明,但用户可能使用的是Windows,所以可能需要提示将dll文件放到应用程序的目录或Qt的插件目录下。 另外,数据库连接参数是否正确,比如主机地址、端口、用户名密码等,如果远程连接可能需要开启MySQL的远程访问权限,或者检查防火墙设置。 还有,SQL语句是否正确执行,比如表是否存在,字段名是否正确,这些都需要在代码中进行错误检查,使用QSqlQuery的lastError()方法输出错误信息,帮助调试。 总结一下,回答的结构应该是先介绍整体流程,然后分步骤说明如何连接数据库、执行SQL、使用模型,并提供示例代码,最后提到常见问题和注意事项。这样用户能一步步跟着操作,同时避免常见错误。</think>在Qt中使用MySQL数据库并通过QSqlQueryModel展示数据的操作流程如下: 1. **环境准备** - 在.pro文件中添加SQL模块:`QT += core gui sql` - MySQL驱动检查:确保编译了QMYSQL插件(需libmysql.dll) 2. **建立数据库连接** ```cpp QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL"); db.setHostName("localhost"); // 数据库服务器IP db.setDatabaseName("testdb"); // 数据库名称 db.setUserName("root"); // 用户名 db.setPassword("123456"); // 密码 db.setPort(3306); // 端口号 if (!db.open()) { qDebug() << "连接失败:" << db.lastError().text(); return; } ``` 3. **执行SQL操作** ```cpp QSqlQuery query; // 创建表 query.exec("CREATE TABLE IF NOT EXISTS users (" "id INT PRIMARY KEY AUTO_INCREMENT," "name VARCHAR(50) NOT NULL," "age INT)"); // 插入数据 query.prepare("INSERT INTO users (name, age) VALUES (?, ?)"); query.addBindValue("张三"); query.addBindValue(25); if(!query.exec()){ qDebug() << "插入失败:" << query.lastError().text(); } // 更新数据 query.exec("UPDATE users SET age=26 WHERE name='张三'"); ``` 4. **使用QSqlQueryModel绑定数据** ```cpp QSqlQueryModel *model = new QSqlQueryModel; model->setQuery("SELECT * FROM users"); // 设置查询语句 // 绑定到QTableView显示 QTableView *tableView = new QTableView; tableView->setModel(model); tableView->show(); // 获取数据示例 QModelIndex index = model->index(0, 1); // 第0行第1列 QString name = model->data(index).toString(); ``` **重要特性说明:** 1. 自动数据绑定:执行setQuery()时会自动获取结果集 2. 只读特性:默认不支持直接修改(如需编辑需用QSqlTableModel) 3. 数据刷新:修改数据后需重新执行查询 ```cpp model->setQuery(model->query().lastQuery()); // 重新执行上次查询 ``` **常见问题处理:** 1. 驱动加载失败:检查MySQL插件路径是否在`<Qt安装目录>/plugins/sqldrivers` 2. 中文乱码问题:建立连接后执行`query.exec("SET NAMES 'UTF8'");` 3. 连接超时处理:在连接字符串中添加参数 ```cpp db.setConnectOptions("MYSQL_OPT_RECONNECT=1;"); ``` **完整示例流程:** ```cpp // 创建模型和视图 QSqlQueryModel *model = new QSqlQueryModel; QTableView *view = new QTableView; // 设置查询并绑定 model->setQuery("SELECT id, name AS 姓名, age AS 年龄 FROM users"); view->setModel(model); view->setWindowTitle("用户信息表"); view->resize(600, 400); view->show(); // 获取查询结果验证 if (model->lastError().isValid()) { qDebug() << "查询错误:" << model->lastError().text(); } else { qDebug() << "获取到" << model->rowCount() << "条记录"; } ``` **注意事项:** 1. 及时释放资源:在窗口关闭时调用`db.close()` 2. 使用预编译语句防止SQL注入 3. 大量数据建议分页查询(通过LIMIT子句) 4. 多线程访问需为每个线程创建独立连接 这种模式适合需要灵活控制SQL语句的场景,兼顾了直接操作SQL的灵活性和Model/View架构的便捷性。对于简单CRUD操作,建议结合使用QSqlTableModel以提高开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值