查看原文
其他

从ORACLE/MySQL到OceanBase:数据库超时机制

MQ4096 数据库技术闲谈 2022-08-27




近期打算写一个【从ORACLE/MySQL到OceanBase】序列。本文是第一篇。6月27日(本周四)晚会在云和恩墨旗主办的DTC线上数据库技术实战峰会做1小时的直播分享,主题为《从ORACLE、MySQL到OceanBase:入门介绍》,敬请关注 敬 https://cs.enmotech.com/event/48 。



概述


本文总结从ORACLE转MySQL、OceanBase开发时经常碰到的一类超时问题的原因。这是由于不同数据库在处理慢SQL、锁等待事件时采取了不同的策略,而业务不了解这个区别就容易碰到问题。第一次使用OceanBase的研发人员也会有这个感受。

注:关于连接超时这个有点复杂,不在这次文章里讨论。

ORACLE对慢SQL的处理


ORACLE在执行SQL时,如果SQL长时间没有返回结果集,ORACLE是不会干预该SQL的运行,连接会一直保持。如果SQL所在事务持有锁了,锁也会一直持有,事务会一直保持。不管SQL执行慢的原因是什么,都是如此。

SQL执行慢的原因可能有两类,一是SQL本身性能不好,需要扫描大量的数据或者大数据集之间做连接等。这种在数据仓库业务里经常出现。二是SQL被其他会话阻塞,通常是SQL在申请某个记录锁,跟其他会话持有该记录的锁冲突了。

ORACLE认为出现这类慢SQL或者阻塞的事情,是应用设计的问题,应交给应用方自己处理。实际上处理最多的通常是运维人员。比如说写个脚本定时查询长时间执行不返回的慢SQL(查询视图v$session_longops),或者查看长时间被阻塞的会话。然后自动发起kill session操作。

针对ORACLE的这个策略,业务研发人员的意见并不统一,褒贬都有。有些数据库连接驱动有设置查询超时功能,推测其原理是发现SQL长时间不返回,框架就自己发起kill session命令。(如果理解不对,欢迎指出)。

MySQL对慢SQL的处理


MySQL里对慢SQL的处理有两种策略。首先针对每个SQL的执行时间有超时参数。如果执行时间达到这个阈值了,就提前返回或者报错。这个参数在5.6叫max_statement_time(单位:毫秒),在5.7以后叫max_execution_time(单位:毫秒)。这个参数默认值是0,即永不超时。(注:MySQL里的参数又叫变量。)

示例:

qingtao_db 02:18:24>set session max_execution_time=7000;
Query OK, 0 rows affected (0.00 sec)

qingtao_db 02:18:30>select * from t3 where id=8 for update;
ERROR 3024 (HY000): Query execution was interrupted, maximum statement execution time exceeded
qingtao_db 02:18:38>select sleep(10);
+-----------+
| sleep(10) |
+-----------+
| 1 |
+-----------+
1 row in set (7.00 sec)

正如前面ORACLE里分析的一样,慢SQL除了自身性能有问题外,还有个可能原因就是被阻塞。MySQL(具体说应该是InnoDB)针对DML SQL在申请锁被阻塞时还会检查等待时间,如果超过参数innodb_lock_wait_timeout(默认秒)时,就报错:Lock wait timeout exceeded; try restarting transaction注意报错时自身事务并不回滚,需要应用捕获数据库异常,主动回滚或提交。这取决于参数innodb_rollback_on_timeout的设置(默认值是OFF)。

qingtao_db 02:42:29>show session variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 5 |
+--------------------------+-------+
1 row in set (0.01 sec)

qingtao_db 02:42:30>select * from t3 where id=8 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

上面的例子DML SQL之所以被阻塞,是因为我在其他会话里更新了该笔记录故意不提交。那个事务就会一直在未提交状态。这种情形在生产环境如果碰到多半是应用有问题。偶尔是业务开发或者运维连接到数据库上更新记录没有提交。MySQL并不会处理长事务,交给业务方或者运维自己处理。

在MySQL里还有些timeout参数是跟连接有关的。比如说connect_timeoutwait_timeoutinteractive_timeout,这些跟SQL查询超时无关,就不在这里说了。

OceanBase对慢SQL和长事务的处理


OceanBase对慢SQL处理只有一种策略。即针对每个SQL的执行时间设置一个超时阈值。参数是ob_query_timeout,默认值是10000000(微秒us),即10秒。这个默认时间很短,所以在OB里跑慢SQL很容易就碰到这个报错(-4012:Timeout)。这个参数是个租户(实例)变量,需要运维手动修改一下。方法如下:

test 02:54:47>show session variables like 'ob_%timeout%';
+---------------------+-----------+
| Variable_name | Value |
+---------------------+-----------+
| ob_query_timeout | 10000000 |
| ob_trx_idle_timeout | 120000000 |
| ob_trx_timeout | 100000000 |
+---------------------+-----------+
3 rows in set (0.01 sec)

test 02:55:06>set session ob_query_timeout=7000000;select sleep(20);
Query OK, 0 rows affected (0.01 sec)

ERROR 4012 (HY000): Timeout
test 02:55:42>
test 02:58:39>set global ob_query_timeout=100000000;
Query OK, 0 rows affected (0.04 sec)

注意,如果是修改租户(实例)变量的范围是全局(global),则需要会话重新连接才会生效。这个机制是跟MySQL保持一致的。也可以在会话层面修改。

也可以在SQL里用Hint设置。示例(为了看效果,故意设置很短时间):

test 03:04:15>select /*+ query_timeout(3000000) */ sleep(10);
ERROR 4012 (HY000): Timeout
test 03:04:19>
test 03:04:46>select /*+ query_timeout(30000000) */ sleep(10);
+-----------+
| sleep(10) |
+-----------+
| 0 |
+-----------+
1 row in set (10.20 sec)

test 03:20:12>select * from t1 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

上面的SQL执行超时通用不管SQL是查询还是修改。OceanBase没有实现类似Innodbinnodb_lock_wait_timeout参数,认为有query_timeout就够了。不过针对DML SQL被阻塞时超时报错,其提示信息还是跟MySQL提示信息一样。

OceanBase比MySQL做得更进一步的是针对未提交事务也增加了两个超时限制。一个是事务闲置超时时间,变量ob_trx_idle_timeout控制,默认值是120000000(微秒us),即120秒。第二个是事务提交超时时间,变量ob_trx_timeout控制,默认值是100000000(微秒us),即100秒。

test 03:11:55>set session ob_trx_timeout=5000000;select now();begin; update t1 set j=j+1;
Query OK, 0 rows affected (0.01 sec)

+---------------------+
| now() |
+---------------------+
| 2019-06-24 15:12:06 |
+---------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.01 sec)

Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0

test 03:12:06>select * from t1;
ERROR 4012 (25000): Transaction is timeout
test 03:12:40>commit;
ERROR 4012 (25000): Transaction is timeout
test 03:12:46>rollback;
Query OK, 0 rows affected (0.01 sec)

test 03:12:48>

备注:

  1. 一旦事务持续时间超过变量ob_trx_timeout设定值,事务状态就会变为超时状态这个错误是SQL标准定义的(参见SQL92标准里的SQLSTATE定义),只不过ORACLE和MySQL没有实现这个,OceanBase实现了它。

  2. 一旦处于超时状态,会话自动释放了事务持有的锁。但是会话本身必须显示的发出rollback命令,否则该会话连接不可用。如果没有发出回滚命令,会话被放回连接池复用了,后续SQL也会报错(-4012)。


OceanBase之所以针对SQL执行和事务提交都设置超时时间,是不想让慢SQL或者被阻塞和阻塞别人的事务长期占有数据库资源(连接、内存等)。通常在互联网业务里,这个慢SQL或者未提交事务并发很高的时候,对数据库的压力会非常大。所以即使超时报错是明智的做法。同样业务如果频繁收到这个报错后,需要分析应用设计是否有不合理的地方。如果业务确实如此,则可以跟运维商量修改租户(实例)这个变量的全局设置。如果不方便修改全局设置(影响所有连接),则特殊的业务可以在会话级别设置这个变量值。比如说批量处理逻辑里,事务可能时间会很长,就需要特别设置这个值。

总结


由上面分析可以看出,针对慢SQL或者未提交的事务,ORACLE、MySQL和OceanBase分别选择了不同的策略去应对。OceanBase是分布式数据库,业务场景并发压力可能非常大,所以OceanBase选择了超时释放资源的策略。业务应用从ORACLE或MySQL平迁到OceanBase时,一定要了解这个细节差异。

如果你有什么问题,欢迎在公众号留言,或者在小程序“墨天轮DBASK”里提问。


推荐阅读



更多后续分享敬请关注公众号:obpilot







您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存