我们也许有过这样的经历:用 mysql 客户端连上数据库,执行一条 SQL,结果迟迟执行不完,我们等得不耐烦了,顺手就是一个 Ctrl + C。Ctrl + C 之后,客户端会干什么,服务端又会发生什么?我们一起来看看。本文内容基于 MySQL 8.0.32 源码,涉及存储引擎为 InnoDB。
mysql -h127.0.0.1 -uroot -v注意:没有使用 begin 显式开启事务,且系统变量 autocommit 的值为 ON。
mysql> UPDATE t1 SET blob1 = REPEAT("这是 blob2 字段", 10240);
--------------
UPDATE t1 SET blob1 = REPEAT("这是 blob2 字段", 10240)
--------------
-- 客户端发送 KILL QUERY 给服务端之后
-- 输出的提示信息
^C^C -- sending "KILL QUERY 11" to server ...
# 服务端执行 KILL QUERY 之后
# 客户端自己的输出信息
^C -- query aborted
-- 服务端返回给客户端的信息
ERROR 1317 (70100): Query execution was interrupted为了方便介绍,我们把执行 Update SQL 的线程称为 Update 线程,执行 KILL QUERY 命令的线程称为 Kill 线程。注意:MySQL 内部是不做这样区分的。KILL QUERY 命令的执行流程如下:第 1 步,Kill 线程根据 query id 查找 Update 线程。如果没有找到,KILL QUERY 命令执行结束;如果找到了,进入第 2 步。
query id 是 show processlist 执行结果中的 id 字段。第 2 步,Kill 线程判断当前连接的 MySQL 用户是否有权限干掉 Update 线程。如果没有权限,KILL QUERY 命令执行结束;如果有权限,进入第 3 步。第 3 步,判断 Update 线程是否正在读写数据字典表。如果不是,Kill 线程继续执行第 4 ~ 6 步;如果是,Kill 线程的使命就到此结束了,接力棒交给 Update 线程。Update 线程读写数据字典表结束,就会马上开始执行 KILL QUERY 命令的第 3 ~ 6 步。
这种情况下,第 3 步会被执行 2 次(Kill 线程和 Update 线程各执行一次)。第 4 步,把 Update 线程的 killed 属性设置为 KILL_QUERY,此时,Update 线程处于被标记为将要被干掉,但是还没有被干掉的状态。
这一步可以想象成城市建设过程中,在要拆迁的房子上写了个大大的拆字,但是房子还立在那里。第 5 步,如果 Update 线程正在等待获取存储引擎中的锁,则放弃等待;如果 Update 线程已经持有存储引擎中的锁,则释放锁。第 6 步,判断 Update 线程是否持有某个条件变量(保存在 current_cond)中。如果持有,发送广播通知正在等待这个条件变量的其它线程,告诉它们可以继续执行了。通过前面的介绍,我们可以看到:不管是 Kill 线程,还是 Update 线程自己执行第 3 ~ 6 步,都只是给 Update 线程打上了 KILL_QUERY 标记,而没有直接把 Update 线程干掉。Update 线程是怎么被干掉的呢?请继续往下看。
// sql/sql_update.cc
// 以下代码处理更新单表的 SQL,例如:
// update t1 set i1 = 100
bool Sql_cmd_update::update_single_table(THD *thd) {
...
while (true) {
// 从存储引擎读取一条记录
error = iterator->Read();
// 如果读取出错(error)
// 或者 thd->killed 不等于 0(也就是 true)
// 对应本文的场景是:线程被打上了 KILL_QUERY 标记
// 直接结束循环
if (error || thd->killed) break;
...
}
...
}int mysql_execute_command(THD *thd, bool first_level) {
...
if ((thd->is_error() && !early_error_on_rep_command) ||
(thd->variables.option_bits & OPTION_MASTER_SQL_ERROR))
trans_/opt/data/workspace_c/mysql8/sql/sql_class.ccrollback_stmt(thd);
else {
/* If commit fails, we should be able to reset the OK status. */
thd->get_stmt_da()->set_overwrite_status(true);
trans_commit_stmt(thd);
thd->get_stmt_da()->set_overwrite_status(false);
}
...
}只有在开启组复制(GROUP REPLICATION)过程中出现错误时,early_error_on_rep_command 才有可能被设置为 true,这里我们先忽略。到这里,KILL QUERY 就算是基本介绍完了。之所以说基本介绍完了,是因为还留有一点点尾巴。前面我们介绍过,Update 线程执行到埋点的时候,如果判断自己已经被标记为即将被干掉,就会中断执行。但是,还有一种很小的可能性,就是 Update 线程执行过程中,已经经过了所有埋点之后,才被标记为即将被干掉,Update 线程也就没有机会中断执行了。这种情况下,就会进入以上代码中的 else 分支,执行 trans_commit_stmt(thd),提交事务。
鉴于进入 else 分支提交事务的可能性很小,我们可以认为只要客户端 Ctrl + C,Update 线程就会中断执行,并回滚事务。
如果即将被干掉的线程(Update 线程)正在读写数据字典表,它会从 kill 线程手上接过接力棒,给自己打上 KILL_QUERY 标记。Update 线程发现自己被打上了 KILL_QUERY 标记,就会中断执行,在 mysql_execute_command() 方法中,会回滚事务。有一点需要说明,前面只是以 Update SQL 为例来介绍 KILL QUERY,其它 SQL 的 KILL QUERY 流程也是一样的。
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput
我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象