精华内容
下载资源
问答
  • 梦中没有错与对,梦中没有恨和悔......四年多前的一个往事大约在2010年的时候,我排查了一个问题。问题描述如下:服务端:Linux Kernel 2.6.8/192.168.188.100客户端:Windows XP/192.168.40.34业务流程

    梦中没有错与对,梦中没有恨和悔...最好闭上你的嘴,这样才算可爱...我不会说:这不公道,我不能接受。我会用朴素的文字记录点点滴滴,早上4点多起来,一气呵成近期的收获与评价,愤怒与忏悔。


    四年多前的一个往事

    大约在2010年的时候,我排查了一个问题。问题描述如下:


    服务端:Linux Kernel 2.6.8/192.168.188.100
    客户端:Windows XP/192.168.40.34
    业务流程(简化版):
    1.客户端向服务端发起SSL连接
    2.传输数据

    现象:SSL握手的时候,服务端发送Certificate特别慢。


    分析:
    具体思路,也就是当时怎么想到的,我已经忘了,但是记住一个结论,那就是纠出了Linux 2.6.8的NAT模块的一个bug。
    在抓取了好多数据包后,我发现本机总是发给自己一个ICMP need frag的报错信息,发现服务端的Certificate太大,超过了本机出网卡的MTU,以下的一步步的思路,最终纠出了bug:

    1.证实服务端程序设置了DF标志。这是显然的,因为只有DF标志的数据包才会触发ICMP need frag信息。


    2.疑问:在TCP往IP发送数据的时候,会检测MTU,进而确定MSS,明知道MSS的值,怎么还会发送超限的包呢?计算错误可能性不大,毕竟Linux也是准工业级的了。


    3.疑问解答:幸亏我当时还真知道一些名词,于是想到了TCP Segment Offload这个技术。

                         TCP Segment Offload简称TSO,它是针对TCP的硬件分段技术,并不是针对IP分片的,这二者区别应该明白,所以这与IP头的DF标志无关。对于IP分片,只有第一个分片才会有完整的高层信息(如   果头长可以包括在一个IP分片中的话),而对于TSO导致的IP数据包,每一个IP数据包都会有标准的TCP头,网卡硬件自行计算每一个分段头部的校验值,序列号等头部字段且自动封装IP头。它旨在提高TCP的性能。


    4.印证:果然服务器启用了TSO


    5.疑问:一个大于MTU的IP报文发送到了IP层,且它是的数据一个TCP段,这说明TCP已经知道自己所在的机器有TSO的功能,否则对于本机始发的数据包,TCP会严格按照MSS封装,它不会封装一个大包,然后让IP去分片的,这是由于对于本机始发而言,TCP MSS对MTU是可以感知到的。对于转发而言,就不是这样了,然而,对于这里的情况,明显是本机始发,TCP是知道TSO的存在的。


    6.猜测:既然TCP拥有对TSO的存在感知,然而在IP发送的时候,却又丢失了这种记忆,从TCP发往IP的入口,到IP分片决定的终点,中间一定发生了什么严重的事,迫使TCP丢失了TSO的记忆。


    7.质疑:这种故障情况是我在公司模拟的,通过报告人员的信息,我了解到并不是所有的情况都会这样。事实上,我一直不太承认是Linux协议栈本身的问题,不然早就被Fix了,我一直怀疑是外部模块或者一些外部行为比如抓包导致的。


    8.可用的信息:到此为止,我还有一个信息,那就是只要加载NAT模块(事实上这是分析出来的,报告人员是不知道所谓的NAT模块的,只知道NAT规则)就会有这个现象,于是目标很明确,死盯NAT模块。


    9.开始debug:由于Linux Netfilter NAT模块比较简单,根本不需要高端的可以touch到内存级的工具,只需要printk即可,但是在哪里print是个问题。


    10.出错点:在调用ip_fragment(就是该函数里面发送了ICMP need frag)之前,有一个判断(省略了不相关的):
    if (skb->len > dst_pmtu(skb->dst) && !skb_shinfo(skb)->tso_size) {
        return ip_fragment(skb, ip_finish_output);
    }

    前一个判断显然为真,如果要想调用ip_fragment的话,后一个判断一定要是假,实际上,如果开启了TSO,就不该调用ip_fragment的。


    11.查找tso_size字段:事情很明显了,一定是哪个地方将tso_size设置成了0!而且一定在NAT模块中(98%以上的可能性吧...),于是在NAT模块中查找设置tso_size的地方。


    12.跟踪ip_nat_fn:这是NAT的入口,进入这个入口的时候,tso_size不是0,可是调用了skb_checksum_help之后tso_size就是0了,问题一定在这个函数中,注意,调用这个help有一个前提,那就是硬件已经计算了校验和。在这个help函数中,有一个skb_copy的操作,正是在这个copy之后,tso_size变成了0,于是进一步看skb_copy,最终定位到,copy_skb_header的最后,并没有将原始skb的tso_size复制到新的skb中,这就是问题所在!


    13.触发条件:什么时候会调用skb_copy呢?很简单,如果skb不完全属于当前的执行流的情况下,按照写时拷贝的原则,需要复制一份。故障现象就是慢,而数据为本机始发,且为TCP。我们知道,TCP在没有ACK之前,skb是不能被删除的,因此当前的skb肯定只是一个副本,因此就需要拷贝一份了。


    14.影响:如此底层的一个函数。搜索代码,影响巨大,各种慢!对于那次的慢,其慢的流程为:socket发送DF数据--感知TSO--丢失TSO--ICMP need frag--TCP裁成小段继续发送...如果禁止了lo的ICMP,那么更慢,因为TCP会触发超时重传,而不是ICMP的建议裁减,并且重传是不会成功的,直到用户程序感知,自行减小发送长度。

    为什么旧事重提

    提起那件事有两个原因,其一是当时没有记录下来整个过程,可是后续的patch却一直在用,最终我自己都快不知其所以然了,其二,是通过那次的分析,按照现在的理解,就可以发现Linux协议栈的一个优化点,即TCP情况下,由于保留了数据skb队列直到ack,那么后续向下的所有skb处理流程都至少要经过一次skb_copy,这种复制操作难道就不能避开吗?如果加载了某些Netfilter钩子,需要对skb进行写操作,这种串行化行为会严重影响Linux网络协议栈的处理效率,这是Netfilter的通病之一。

    附:skb操作的优化点

    1.如果把数据和元数据彻底分开是不是更好呢?
    2.进一步将写操作的粒度细分

       有些写操作是针对每一个数据包的,这些不得不复制,但是能否局部复制,然后采取分散聚集IO进行拼接呢?尽量采用指针操作而不是复制数据本身,这正是借鉴了UNIX fork模型以及虚拟地址空间的COW。如果把skb的空间进行细粒度划分,那么就可以做到,需要COW哪部分就只有那部分,不会导致全局复制。

    前几天的一个TCP问题排查过程

    现象与过程

    早就习惯了那种惊心动魄的三规制度(规定的时间,规定的地点,和规定的人一起解决问题),反而不习惯了按部就班了。事情是这样的。

           周末的时候,中午,正在跟朋友一起聊天吃饭,收到了公司的短信,说是有一个可能与TCP/IP有关的故障,需要定位,我没有随即回复,因为这种事情往往需要大量的信息,而这些信息一般短信传来的时候早就经过了N手,所以为了不做无用功,等有关人员打电话给我再说吧。

           ...


     (以下描述有所简化)
    我方服务端:Linux/IP不确定(处在内网,不知道NAT策略以及是否有代理以及其它七层处理情况)
    测试客户端:Windows/192.168.2.100/GW 192.168.2.1
    中间链路:公共Internet
    可用接入方式:3G/有线拨号
    服务端设备:第三方负载均衡设备。防火器等
    业务流程:客户端与服务端建立SSL连接
    故障:
    客户端连接3G网卡使用无线链路,业务正常;客户端使用有线链路,SSL握手不成功,SSL握手过程的Client Certificate传输失败。


    分析:

    1.通过抓包分析,在有线链路上,发送客户端证书(长度超过1500)后,会收到一条ICMP need frag消息,说是长度超限,链路MTU为1480,而实际发送的是1500。通过无线链路,同样收到了这个ICMP need frag,只是报告的MTU不同,无线链路对应的是1400。


    2.有线链路,客户端接受ICMP need frag,重新发送,只是截掉了20字节的长度,然而抓包发现客户端会不断重传这个包,始终收不到服务端的ACK,其间,由于客户端久久不能发送成功数据到服务端,服务端会回复Dup ACK,以示催促。


    3.猜想:起初,我以为是时间戳的原因,由于两端没有开启TCP时间戳,所以在RTT以及重传间隔估算方面会有误差,但是这不能解释100%失败的情形,如果是由于时间戳计算的原因,那不会100%失败,因为计算结果受波动权值影响会比较大。


    4.对比无线链路,和有线链路的唯一区别就是ICMP报告的MTU不同。


    5.中途总结:
    5.1.此时,我并没有把思路往运营商链路上引导,因为我始终认为那不会有问题,同样,我也不认为是SSL的问题,因为错误总是在发送大包后呈现,事实上,接受了ICMP need frag后,之前发的那个超限包已经被丢弃,重新发送的是一个小一点的包,对于TCP另一端来讲,这是完全正常的。
    5.2.根本无需查看服务日志,因为还没有到达那个层次。抓包结果很明确,就是大包传不过去,其实已经按照MTU发现的值传输了,还是过不去,而无线链路能过去。因此应该不是MTU的问题。

    5.3.除了运营商链路,MTU,服务端处理之外,还会是哪的问题呢?事实上,程序的bug也不是不可能的,或者说是一些不为人知的动作,不管怎样,需要隔离问题。


    6.猜测是中间某台设备没法处理大包,这个和MTU没有关系,可能就是它处理不了或者根本上不想处理大包,多大呢?反正1480的包处理不了,减去IP头,TCP头,剩余的是1440的纯数据。于是写一个简单的TCP client程序,在TCP握手完成后马上发送(为了防止由于不是Client Hello而主动断开,因此必须马上发,只是为了观察针对大包的TCP ACK情况,此时与服务无关)长度1440的数据,验证!


    7.果然没有ACK迅速返回,客户端不断重试发送1440的包(之后10秒到20秒,会有ACK到来,但不是每次都会到来,这明显是不正常的)。为了证明这种方式的合理性,发送无线链路上MTU限制的数据大小,即1400-20-20=1360的数据,ACK秒回。因此猜测中间设备的数据包处理的长度临界点在1360和1440之间。


    8.经过不断的测试,二分法查询临界点,找到了1380是可处理长度临界点。发送1380的纯数据是正常的,发送1381的纯数据就不正常了。抓包的目标地址是12.23.45.67,简称MA,现在不确定的是MA是什么,是我方的设备,还是它方的设备,如果是我方的设备,排错继续,如果不是,排错终止。总之,1380这个临界点是一个疑点,常规来讲是不正常的,但也不能排除有这么限制的正常理由。无线链路没有问题是因为无线链路的MTU比较小,最大纯数据长度1360小与临界值1380。


    9.补充测试,模拟问题机器,将其本机的MTU改为1380+20+20=1420,传输也是正常的,然而改为1421,就不行了。(注意,只有本机的MTU修改才有效,因为只有TCP数据始发设备,MSS才与MTU关联)


    .....


    1x.第9步后面的排查我没有参与,但是最终,我方设备确实没有收到客户端SSL握手过程传出的证书,说明确实是中间设备阻止了这个”大包“的传输,至于它到底是谁,到底怎么回事,与我们无关了,但对于我个人而言,对其还是比较感兴趣的。

    对于该次排错的总结

    这是一个典型的网络问题,涉及到IP和TCP,细节不多,但足够典型。其实这个问题与最终的业务逻辑没有关系,但是事实往往是,只有在业务逻辑无法正常时,这类底层的问题才会暴露,这是TCP/IP协议栈的性质所致。此类问题的排查要点在于,你要用最快的速度把它与高层协议隔离开来,并且不能陷入任何细节。
    TCP细节:为何不必考虑TCP细节?这类场景既不特殊,又不复杂,如果陷入TCP细节的话,会掩盖或者忽略大量横向的问题,比如你会死盯着TCP的重传机制做细致研究,或者细致地研究RTT计算方法,最终也不一定能得到什么结论。换句话说,你一定要相信TCP是正常的。
    服务程序细节:这个也是要隔离的。因为服务器并没有真的开始服务,且故障是100%重现的,因此可以确定这不是什么复杂的问题所导致,真正复杂的问题往往不是100%重现,即便是你挖掘出其重现规律,也够你喝一壶的。
    TCP问题和IP问题的相异:它们虽然都是网络协议栈的一员,但是使用方式却大不相同。实际上TCP提高了使用者的门槛,一般而言,TCP是让程序去使用的,因此你要想TCP跑起来,起码要理解其大致原理,或者说懂socket机制,如果你上网浏览网页,虽然也是用的TCP,它确实跑起来了,但是使用者不是你,而是你的浏览器。IP就不同,IP的配置者可以是小白,并且随意配置都不会报错。再往下,布线问题,拓扑问题,几乎没有什么门槛,但是却更加容易出错。因此首先要排除的就是这类问题。
    防火墙策略或者程序BUG:实际上,第一步就需要询问管理员,是不是防火墙上特殊的策略所致,然而对于无法得到这个消息的时候,你就不能从这儿开始了。接下来,与之平等的是怀疑程序的处理BUG,此时,隔离出原有的业务逻辑细节是重要的,现象是大包无法收到ACK,此时就要忽略掉这个大包的内容以及其上下文,直接发送一个任意大包进行测试。

           因此,这类问题的排查是一个逐步隔离的过程,相对四年前的那次NAT bug的排查,这个故障在技术上要更容易些,所有的复杂性和时间的耽搁全部在人员协调交流上,人员之间信息的误传或者漏传也是一个难点,四年前的那个NAT bug,是一个技术上更加深入的问题,涉及到了内核协议栈代码级别,同时在此之前,我还要找到这个点,然而它的容易点在于,这个问题只涉及到我一个人,而且也是100%重现。

    天与地,贵在没有记忆,一切伤痕总是会被冲刷,一切荣耀,总是会了无痕迹......
    展开全文
  • (尊重劳动成果,转载请注明出处:https://yangwenqiang.blog.csdn.net/article/details/90544530冷血...MySQL原理与实践():一条select语句引出Server层和存储引擎层 MySQL原理与实践(二):一条update语句引...

    (尊重劳动成果,转载请注明出处:https://yangwenqiang.blog.csdn.net/article/details/90544530冷血之心的博客)

    关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~

    快速导航:

     MySQL原理与实践(一):一条select语句引出Server层和存储引擎层

    MySQL原理与实践(二):一条update语句引出MySQL日志系统

    MySQL原理与实践(三):由三种数据结构引入MySQL索引及其特性

    MySQL原理与实践(四):由数据库事务引出数据库隔离级别

    MySQL原理与实践(五):数据库的锁机制

    MySQL原理与实践(六):自增主键的使用

    目录

    前言:

    正文:

    MySQL基本逻辑架构图:

    Server层:

    连接器:

    查询缓存:

    分析器:

    优化器:

    执行器:

    总结:

    存储引擎层:

    InnoDB存储引擎:

    InnoDB的特性如下:

    MyISAM存储引擎:

    MyIASM存储引擎的特性如下:

    MyIASM存储引擎的应用场景:

    Memory存储引擎:

    InnoDB 和 Memory 引擎的数据组织方式是不同的:

    InnoDB和Memory的不同:

    总结:

    结束语:


    前言:

           在这篇博文之前,博主也曾写过一些入门级别的MySQL相关文章,但是鉴于当时的技术水平和知识沉淀,部分概念和原理并没有理解清楚,仅仅是做为笔记在供大家参考学习。这段时间有时间和精力重新系统学习MySQL相关知识,极客时间 -《MySQL实战45讲》,感觉在深度和广度上都有了些许精进,特此开启了MySQL原理与实践的系列文章的攥写,希望帮助更多的人理清基本原理和概念。

     

    正文:

         我们先来写一条最普通的SQL查询语句:

    mysql> select * from T where ID=10;
    

    没问题,这个语句只要我们稍微有点数据库基础应该都可以看懂,那就是从表T中找到ID=10的数据,并且输出该行所有字段。那么问题来了,这个语句在MySQL内部是如何执行的?下边通过介绍MySQL的基本逻辑架构图来详细阐述。

    MySQL基本逻辑架构图:

                               

            从上图可以看出,MySQL内部逻辑架构包括Server层以及下边的存储引擎层。Server层又包括连接器,查询缓存,分析器,优化器和执行器。存储引擎层则包括当前数据库所使用的存储引擎,常见的包括:InnoDB,MyISAIM以及Memory等。接下来我们依次介绍逻辑架构图中出现的各个组件。

    Server层:

    连接器:

         由图中可以看到,每一个客户端都是和Server层的连接器建立连接,连接器负责客户端与数据库建立连接,获取权限,维持和管理连接。通过以下命令来建立连接:

    mysql -h$ip -P$port -u$user -p
    

    在这行命令之后,我们在交互命令中输入密码即可建立连接。(强烈不建议直接将密码附在-p后边,会导致密码泄漏)。

    连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证你的身份,这个时候用的就是你输入的用户名和密码。

    • 如果用户名或密码不对,你就会收到一个"Access denied for user"的错误,然后客户端程序结束执行。
    • 如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。

    这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。

           建立连接之后,我们可以通过show processlist来查看已经建立的连接。如果客户端一段时间内没有活跃行为,那么连接器在默认的8个小时后主动断开连接。如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执行请求了。

    短连接:每次查询几次之后会断开,再次查询需要重新建立连接。(成本较高)

    长连接:长连接会导致内存OOM,导致MySQL异常重启。

    那么如何解决长连接导致的OOM问题?

    • 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。

    • 如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

    查询缓存:

            这个查询缓存比较好理解,再每一次的查询时,我们都先去看看是否命中缓存,命中则直接返回,提高了系统的响应速度。但是这个功能有一个相当大的弊病,那就是一旦这个表中数据发生更改,那么这张表对应的所有缓存都会失效。

            对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。

            在MySQL 8.0 版本之前,我们可以通过将 参数query_cache_type 设置成 DEMAND,来关闭查询缓存的功能;在MySQL8.0版本之后直接完全删掉了这部分功能。

    分析器:

           系统在真正执行你输入的语句之前,必须分析出你的语句想要干嘛?首先通过select关键字得知这是一条查询命令,还包括分析你要查询的是哪张表以及查询条件是什么?同时,分析器必须分析你输入语句的语法正确性。相信我们都遇到过这个错误吧?  “You have an error in your SQL syntax” 

    优化器:

            优化器是MySQL用来对你输入的语句在真正执行之前所做的最后一步优化。优化内容包括:选择哪个索引?是否选择索引?多表查询的联合顺序等。 每一种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。

    执行器:

           MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限)。

    mysql> select * from T where ID=10;
     
    ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
    

          如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。比如我们这个例子中的表 T 中,ID 字段没有索引,那么执行器的执行流程是这样的:

    • 调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
    • 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
    • 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

    至此,这个语句就执行完成了。

           对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。

         在数据库的慢查询日志中看到一个 rows_examined 的字段表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟 rows_examined 并不是完全相同的。

    总结:

    MySQL的Server层主要包括:连接器,查询缓存,分析器,优化器,执行器。多个组件的共同配合,我们的SQL命令才可以执行成功。

    存储引擎层:

            MySQL的存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。

    我们来创建一张简单的表:

    mysql> create table T(c int) engine=InnoDB

    通过engine来指定当前表所使用的数据存储引擎,通俗的说就是这一张表的类型。不同存储引擎的表数据存取方式不同,支持的功能也不同。接下来我们主要介绍 InnoDB、MyISAM、Memory 这三种存储引擎。

    InnoDB存储引擎:

          InnoDB是当前MySQL的默认的存储引擎,也是互联网等公司数据库存储引擎的不二选择。

    InnoDB的特性如下:

    • 支持数据库事务,在可重复读的隔离级别下,通过MVCC解决了不可重复读的问题,通过间隙锁的引入解决了幻读的问题
    • 支持行级锁和表级锁,默认是行级锁,更小的锁粒度意味着更高的并发度。
    • 支持外键
    • 为处理巨大数据量时的最大性能设计,它的CPU效率可能是任何其它基于磁盘的关系数据库引擎所不能匹敌的。
    • InnoDB中不保存表的行数(eg:select count(*) from table时,InnoDB需要扫描一遍整个表来计算有多少行);清空整个表时,InnoDB是一行一行的删除,效率非常慢。
    • InnoDB使用B+ Tree来做索引,查询效率高,支持索引上的范围查询

    关于InnoDB的特性,我们在之后的文章中详细阐述。

    MyISAM存储引擎:

            在MySQL5.1版本之前,其默认的存储引擎是MyISAM。MyISAM 管理非事务表、是ISAM 的扩展格式。除了提供ISAM里所没有的索引的字段管理等的大量功能、MyISAM 还使用一种表格锁定的机制、来优化多个并发的读写操作。MyISAM 提供高速存储和检索、以及全文搜索能力。

    MyIASM存储引擎的特性如下:

    • 不支持事务、不具备AICD特性(原子性、一致性、分离性、永久性)
    • 表级别锁定形式(更新数据时锁定整个表、这样虽然可以让锁定的实现成本很小但是同时大大降低了其并发的性能)
    • 读写相互阻塞(不仅会在写入的时候阻塞读取、还会在读取的时候阻塞写入、但是读取不会阻塞读取)
    • 只会缓存索引(myisam通过key_buffer_size来设置缓存索引,提高访问性能较少磁盘IO的压力、但是只缓存索引、不缓存数据)
    • 读取速度快、占用资源比较少
    • 不支持外键约束、只支持全文检索

     MyIASM存储引擎的应用场景:

    • 不需要事务支持的场景
    • 读多或者写多的单一业务场景、读写频繁的则不适合、会阻塞
    • 读写并发访问较低的业务
    • 数据修改相对较少的业务
    • 以读为主的业务
    • 对数据的一致性要求不是很高的业务
    • 服务器硬件资源相对比较差的机器

    Memory存储引擎:

           数据库中的表如果使用了Memory存储引擎,那么也可以将这张表称为内存表。为了说明内存表的相关特性,我们先来创建两张表:

    create table t1(id int primary key, c int) engine=Memory;
    create table t2(id int primary key, c int) engine=innodb;
    insert into t1 values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(0,0);
    insert into t2 values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(0,0);
    

    t1是一张内存表,t2则是一张InnoDB表,我们分别插入了同样的数据。并且执行 select * from t1 和 select * from t2

           可以看到,内存表 t1 的返回结果里面 0 在最后一行,而 InnoDB 表 t2 的返回结果里 0 在第一行。这就是内存表的一个特性,内存表的索引使用了hash索引,InnoDB表则使用了B+ Tree索引。

           如果你熟悉InnoDB的B+ Tree索引(不熟悉也没关系,后边文章会介绍),则肯定知道t2表的数据组织方式为一颗B+ Tree,并且其主键索引树上的子节点包含了所有的数据,并且是有序的。所以从t2表中查询所有数据,结果是有序的。

           内存表的数据和索引是分开的,数据部分以数组的方式单独存放。主键id是一个hash索引,索引上的 key 并不是有序的。在内存表 t1 中,当我执行 select * 的时候,走的是全表扫描,也就是顺序扫描这个数组。因此,0 就是最后一个被读到,并放入结果集的数据。

    InnoDB 和 Memory 引擎的数据组织方式是不同的:

    • InnoDB引擎把数据放在主键索引上,其他索引上保存的是主键 id。这种方式,我们称之为索引组织表(Index Organizied Table)
    • Memory引擎采用的是把数据单独存放,索引上保存数据位置的数据组织形式,我们称之为堆组织表(Heap Organizied Table)

    InnoDB和Memory的不同:

    • InnoDB 表的数据总是有序存放的,而内存表的数据就是按照写入顺序存放的

    • 当数据文件有空洞的时候,InnoDB 表在插入新数据的时候,为了保证数据有序性,只能在固定的位置写入新值,而内存表找到空位就可以插入新值

    • 数据位置发生变化的时候,InnoDB 表只需要修改主键索引,而内存表需要修改所有索引

    • InnoDB 表用主键索引查询时需要走一次索引查找,用普通索引查询的时候,需要走两次索引查找。而内存表没有这个区别,所有索引的“地位”都是相同的

    • InnoDB 支持变长数据类型,不同记录的长度可能不同;内存表不支持 Blob 和 Text 字段,并且即使定义了 varchar(N),实际也当作 char(N),也就是固定长度字符串来存储,因此内存表的每行数据长度相同。

    为了验证上边的不同点2,我们将t1表中的数据id=5删除,并且插入新的id=10,查询全部结果观察。执行的SQL语句如下:

    delete from t1 where id=5;
    insert into t1 values(10,10);
    select * from t1;

    结果如下:

    由返回结果我们可以看出,id=10 这一行出现在 id=4 之后,也就是原来 id=5 这行数据的位置。

            Memory表的主键索引是哈希索引,缺点是执行范围查询如:select * from t1 where id<5; 会走全表扫描,效率低下。

    解决办法:Memory表也支持B+ Tree的索引方式,通过如下的语句可以在id列上同时建立B+ Tree索引。

    alter table t1 add index a_btree_index using btree (id);
    

    由结果我们可以看出,当前查找出来的数据是有序的,证实了当前确实建立了B+ Tree的索引,并且优化器在范围查询的时候帮我们选择了B+ Tree索引。 我们可以强制使用默认的主键索引方式来验证:

          图中我们使用了force语句强行指定使用的主键默认索引(hash索引),再次走了全表扫描,返回了无序(和插入数据顺序有关)的数据。

            通过名字内存表,我们可以知道Memory存储引擎的表数据都存在内存中,通过参数max_heap_table_size控制Memory表的大小,读写速度很快。并且内存表支持hash索引和B+ Tree索引,但是我们为什么一般不会使用Memory做为表的存储引擎呢?主要考虑以下两点:

    • 锁的粒度问题
    • 数据持久化问题

    (1)锁的粒度问题:

    内存表不支持行锁,只支持表锁。因此,一张表只要有更新,就会堵住其他所有在这个表上的读写操作

    (2)数据持久化问题:

    数据放在内存中,是内存表的优势,但也是一个劣势。因为,数据库重启的时候,所有的内存表都会被清空。特别是在主从结构下,主库正常更新update数据,但是从库异常重启,导致数据丢失,从库重启后,同步主库的内容会由于找不到该数据行而报错,导致主从同步停止。

    基于Memory表的特点和缺点,哪些场景适合使用Memory做为其存储引擎呢?

    答:在数据量可控的情况下,我们所需的临时表可以使用内存表。

    内存临时表刚好可以无视内存表的两个不足,主要是下面的三个原因:

    • 临时表不会被其他线程访问,没有并发性的问题

    • 临时表重启后也是需要删除的,清空数据这个问题不存在

    • 备库的临时表也不会影响主库的用户线程。

    总结:

    在存储引擎层我们介绍了InnoDB,MyIASAM以及Memory存储引擎,由于InnoDB和MyIASM比较常见和常用,我们后边改回继续介绍,所以这里着重介绍了Memory内存表的相关特点和使用场景。总之,不同的存储引擎提供了不同的数据读写接口。

     

    结束语:

     这篇文章是MySQL原理与实践系列文章的第一篇,文中内容整理总结于博主的学习与实践中,参考了网上各位同学的成果。之后会继续更新该系列文章,希望对大家有帮助。

     

    如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,可以进群366533258一起交流学习哦~

    本群给大家提供一个学习交流的平台,内设菜鸟Java管理员一枚、精通算法的金牌讲师一枚、Android管理员一枚、蓝牙BlueTooth管理员一枚、Web前端管理一枚以及C#管理一枚。欢迎大家进来交流技术。
     

    关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~

    展开全文
  • 我们公司的项目都是前后端分离的,上线几个月以来,发现一个很奇怪的问题,每次前端发起请求,通过浏览器的开发者工具都能看到在Network下同一个url有两条请求,第一条请求的Method为OPTIONS,第二条请求的Method才...

    我们公司的项目都是前后端分离的,上线几个月以来,发现一个很奇怪的问题,每次前端发起请求,通过浏览器的开发者工具都能看到在Network下同一个url有两条请求,第一条请求的Method为OPTIONS,第二条请求的Method才是真正的GET或者POST,并且,第一条请求无数据返回,第二条请求才会返回正常的数据。

    发现这个问题之后,立即组织搜索问题产生的原因以及解决方案。在网上搜索了大量资料,得到的一个结论是:第一个OPTIONS的请求是由Web服务器处理跨域访问引发的。网上资料显示,OPTIONS是一种“预检请求”,浏览器在处理跨域访问的请求时如果判断请求为复杂请求,则会先向服务器发送一条预检请求,根据服务器返回的内容浏览器判断服务器是否允许该请求访问。如果web服务器采用cors的方式支持跨域访问,在处理复杂请求时这个预检请求是不可避免的。

    查询代码发现,我们的web服务器确实采用的是cors来解决跨域访问的问题,并且我们在header中添加了自定义参数,导致我们的每次请求都为复杂请求,从而产生了每次请求都会发送两条请求的现象。

    问题的原因找到了,就要想办法解决这个问题。既然浏览器在处理复杂请求时,不可避免的要发送预检请求,那么能否减少预检请求的次数呢?比如,预检一次设置一个有效期,在有效期内不再重复预检。顺着这个思路,继续搜索相关资料,最终发现设置Access-Control-Max-Age这个参数即可达到预期目标。该参数用来指定本次预检请求的有效期,单位为秒。在服务器上设置该参数之后,问题解决了,大快人心!!!

    参考资料:http://blog.csdn.net/charleslei/article/details/51906635

    展开全文
  • (尊重劳动成果,转载请注明出处:https://yangwenqiang.blog.csdn.net/article/details/90574620冷血...MySQL原理与实践():一条select语句引出Server层和存储引擎层 MySQL原理与实践(二):一条update语句引...

    (尊重劳动成果,转载请注明出处:https://yangwenqiang.blog.csdn.net/article/details/90574620冷血之心的博客)

    关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~

    快速导航:

     MySQL原理与实践(一):一条select语句引出Server层和存储引擎层

    MySQL原理与实践(二):一条update语句引出MySQL日志系统

    MySQL原理与实践(三):由三种数据结构引入MySQL索引及其特性

    MySQL原理与实践(四):由数据库事务引出数据库隔离级别

    MySQL原理与实践(五):数据库的锁机制

    MySQL原理与实践(六):自增主键的使用

    目录

    前言:

    正文:

    redo log (重做日志模块)

    磁盘IO和内存读写效率的关系:

    WAL(Write-Ahead Logging技术):

     redo log示意图:

    redo log存储的数据结构如下所示:

    为什么redo log写入效率比直接写磁盘效率高?

    redo log和buffer pool的关系:

    binlog(归档日志模块)

    为什么我们还需要binlog日志?

    redo log和binlog日志的不同点如下:

    binlog恢复数据库:

    一条update语句在MySQL内部是如何执行的:

    两阶段提交协议:

    redo log 和 binlog 是怎么关联起来的?

    为什么处于 prepare 阶段的 redo log 加上完整 binlog,重启就能恢复:

    如果 redo log 写完,再写 binlog。崩溃恢复的时候,必须得两个日志都完整才可以,这种设计可以吗?

    日志模块总结:

    结束语:


    前言:

            这是MySQL原理与实践的第二篇文章,通过一条update更新语句来引出MySQL日志系统,主要包括redo log(重做日志)模块和binlog(归档日志)模块。虽然,MySQL中还有undo log(回滚日志)的概念,但是回滚日志主要用来一致性视图中实现MVCC,所以undo log的相关介绍,我们留在后边介绍学习事务相关知识点时再行介绍。本文整理总结于极客时间 -《MySQL实战45讲》,欢迎大家订阅学习,干货满满。

    正文:

           假设现在有一张表,建表语句如下:

    mysql> create table T(ID int primary key, c int);

           我们再来看一个简单的更新语句:

    mysql> update T set c=c+1 where ID=2;

           通过上一篇文章的学习,我们知道了MySQL的内部逻辑架构图,包括Server层的连接器,查询缓存,分析器,优化器以及执行器;存储引擎层的存储引擎。如下图所示:

                                                 

            查询语句和更新语句执行的内部逻辑基本上都是一样的,但是更新语句涉及到了数据的更改,所以必不可少的需要引入日志模块,即redo log重做日志模块和binlog归档日志模块。接下来,我们依次介绍这两个重要的日志模块。

    redo log (重做日志模块)

    磁盘IO和内存读写效率的关系:

           在介绍redo log之前,我们先来了解下磁盘和内存的区别与联系。我们知道,MySQL是一种关系型数据库,数据持久化保存在磁盘上。但是我们都知道,磁盘的IO效率很低,与之对应的是内存的IO效率远高于磁盘IO。

           在MySQL中,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题,MySQL采用了另外一种更新机制,先更新保存在内存中,写入内存成功,即表示该数据更新成功,给客户端返回。随后,在一个数据库空闲的时间段或者是内存占满之后,将内存中的数据刷到磁盘上。

    WAL(Write-Ahead Logging技术):

            这么做有什么问题吗?当然有问题,如果发生异常重启的现象,那么内存中的数据将会丢失,出现数据不一致的情况。那么如何解决呢?这个时候,我们的redo log重做日志就该闪亮登场了。在更新数据写入内存的同时,我们会记录redo log,并且持久化到磁盘,当数据库异常宕机之后,我们可以根据redo log重做日志来恢复数据,保证之前提交的数据不会丢失,也就是拥有了crash-safe的能力。同时这就是我们常说的WAL技术,即Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。

     redo log示意图:

             InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示:

            write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。

            write pos 和 checkpoint 之间的是redo log还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示redo log满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。

           有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。

    redo log存储的数据结构如下所示:

    • reod_log_type: 占用1字节,表示重做日志类型。各种不同操作有不同的重做日志格式,但有基本的格式

    • space:表空间的ID,采用压缩的方式,占用空间可能小于4字节

    • page_no:页的偏移量,同样采用压缩方式

    • redo_log_body:每个重做日志的数据部分,恢复时需要调用相应的函数解析。

    为什么redo log写入效率比直接写磁盘效率高?

           我们知道,redo log是一种日志文件,必然也是持久化到磁盘上的。那为什么先更新内存,然后写redo log会比数据直接写磁盘效率高呢?原因在于redo log可以循环并且顺序写入磁盘,数据直接写入磁盘多了查找源数据和随机写入的过程,自然效率会低下很多。

    redo log和buffer pool的关系:

          我们知道,更新数据其实就是先写内存,同时记录redo log,那么就会返回当前数据已经更新成功。此时,内存中的数据页和磁盘的数据页是不一致的,称此为脏页。正常情况下,数据库会在适当的时候将buffer pool里边的脏页刷新到磁盘上。整个过程其实和redo log是没有任何关系的。只有在崩溃恢复场景中,InnoDB 如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让 redo log 更新内存内容。更新完成后,内存页变成脏页,之后会被刷新到磁盘。

     

    binlog(归档日志模块)

            MySQL的架构包括Server层和引擎层。前者是MySQL功能层面的事情,引擎层负责存储相关的具体事宜。redo log是InnoDB引擎特有的日志模块;binlog是Server层自带的日志模块。

    为什么我们还需要binlog日志?

            最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。

    redo log和binlog日志的不同点如下:

    • redo log是InnoDB引擎特有的日志模块;binlog是Server层自带的日志模块

    • redo log是物理日志,记录了某个数据页上所做的修改;binlog是逻辑日志,记录本次修改的原始逻辑,说白了就是记录了修改数据的SQL语句

    • redo log是循环写的形式,空间固定会被用完;binlog是追加写的形式,可以写多个文件,不会覆盖之前的日志。

            我们可以通过mysqlbinlog可以解析查看binlog日志,在MySQL中binlog的日志格式有statement,row以及mixed三种方式,为了可以更加准确的记录归档日志,我们一般选择row格式做为binlog的日志格式。特性对比如下:

    binlog恢复数据库:

            binlog是一种归档日志,只要binlog存在,我们就可以进行数据库的恢复。假如说,在今天中午12点的时候,发现上午10点执行了错误的SQL语句,想把数据库状态恢复到上午10点,错误语句执行之前。那么该怎么办呢?

    数据恢复步骤如下:

    • 首先你要拿到最近一次的备份库
    • 拿到最近备份库开始到出错时候的所有binlog(binlog日志保留时间可以自定义)
    • 使用binlog重放到错误发生之前。

    一条update语句在MySQL内部是如何执行的:

    在简单介绍了redo log和binlog之后,我们回到文章开头,分析一条update语句在MySQL内部是如何执行的:

    • 执行器要先找存储引擎找到这一行数据,从内存中或者磁盘中,返回给执行器
    • 执行器拿到数据之后进行更新操作,再调用引擎接口写入这行新数据
    • 引擎将这行数据更新到内存中,并且将操作记录写入redo log中,此时redo log处于prepare状态。然后告知执行器执行完成随时可以提交事务了
    • 执行器生成这个操作的binlog,并且将binlog写入磁盘
    • 执行器调用引擎的提交事务接口,引擎将刚刚写入的redo log改成提交(commit)状态,更新完成

    update语句的执行逻辑图如下所示:

                                        

    两阶段提交协议:

    由执行逻辑图可以看出,这里其实是使用了两阶段提交,如果不适用两阶段提交会有如下的问题:

    • 先写redo log,后写binlog。会导致异常重启后redo log多了,binlog缺失。使用binlog恢复的备份库会缺少一条事务
    • 先写binlog,后写redo log。会导致多一条事务出来
    • 总结:不使用两阶段提交会导致数据库状态和用日志恢复出来的数据库状态不一致

    简单说,redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。

           既然两阶段提交可以保证数据的一致性,那么根据上图update执行示意图,我们来分析下异常发生的时刻对于数据一致性的影响:

    • 若在写入binlog日志之前发生异常crash:由于此时 binlog 还没写,redo log 也还没提交,所以崩溃恢复的时候,这个事务会回滚。这时候,binlog 还没写,所以也不会传到备库。
    • 若binlog日志写完之后发生了异常crash:
      • 崩溃恢复时的判断规则:
        • 如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交。
        • 如果 redo log 里面的事务只有完整的 prepare,则判断对应的事务 binlog 是否存在并完整:
          • a. 如果是,则提交事务
          • b. 否则,回滚事务

    那么如何判断binlog日志是否完整?

    其实每一种格式的binlog日志都是由固定格式的,并且在MySQL 5.6.2 版本以后,还引入了 binlog-checksum 参数,用来验证 binlog 内容的正确性。对于 binlog 日志由于磁盘原因,可能会在日志中间出错的情况,MySQL 可以通过校验 checksum 的结果来发现。

    redo log 和 binlog 是怎么关联起来的?

    它们有一个共同的数据字段,叫 XID。崩溃恢复的时候,会按顺序扫描 redo log:

    • 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;
    • 如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。

    为什么处于 prepare 阶段的 redo log 加上完整 binlog,重启就能恢复:

           这和数据与备份的一致性有关。在 binlog 写完以后 MySQL 发生崩溃,这时候 binlog 已经写入了,之后就会被从库(或者用这个 binlog 恢复出来的库)使用。所以,在主库上也要提交这个事务。采用这个策略,主库和备库的数据就保证了一致性。

    如果 redo log 写完,再写 binlog。崩溃恢复的时候,必须得两个日志都完整才可以,这种设计可以吗?

           事务的持久性问题,对于 InnoDB 引擎来说,如果 redo log 提交完成了,事务就不能回滚(如果这还允许回滚,就可能覆盖掉别的事务的更新)。而如果 redo log 直接提交,然后 binlog 写入的时候失败,InnoDB 又回滚不了,数据和 binlog 日志又不一致了。

    日志模块总结:

    • redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log都直接持久化到磁盘。这个参数设置成 1,可以保证MySQL 异常重启之后数据不丢失。
    • binlog是一种归档日志。sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘,可以保证 MySQL 异常重启之后 binlog 不丢失。

    结束语:

            这篇文章是MySQL原理与实践系列文章的第二篇,结合一条update更新语句,主要介绍了MySQL中重要的日志相关模块,并且进行了相应的概念总结。之后会继续更新该系列文章,希望对大家有帮助。

     

    如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,可以进群366533258一起交流学习哦~

    本群给大家提供一个学习交流的平台,内设菜鸟Java管理员一枚、精通算法的金牌讲师一枚、Android管理员一枚、蓝牙BlueTooth管理员一枚、Web前端管理一枚以及C#管理一枚。欢迎大家进来交流技术。

    关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~

    展开全文
  • 输出结果为 12 12 ...3:执行main方法的第一条指令,new B(); 这句话就是给B类实例对象分配堆空间。因为B继承A父类,所以,虚拟机首先加载A类到方法区,并在堆中为父类成员变量在子类空间中初始化,
  • GPRS调试中发现的一些问题。以及后续发现STM32串口发送字符串产生第一个字符丢失现象的解释与解决方法。
  • 一个vc内嵌asm的BUG引出的...

    千次阅读 2006-05-24 01:39:00
    在语法上, 我们通常认为以下的两条语句是等价的:mov ecx, offset DATA_LABLE //其中DATA_LABLE是数据定义标签lea ecx, DATA_LABLE而更进一步, 我们也会认为以下两句是等价的:mov ecx, ebp-8lea ecx, [ebp-8]第种,...
  • Unity两点之间生成一条直线

    千次阅读 2020-06-15 12:02:24
    private void GetBetweenPoint(Transform target, Vector3 start,Vector3 end,float percent=1f) { Vector3 normal = (end - start).normalized; float distance = Vector3.Distance(start, end);...
  • 引入题目:List中的数据如何根据对象的某一个或多个字段排序?   第一节 对于引入题目的探讨 首先把引入题目表述的清楚一些,在一个List中存储的是一些对象实例,而对象实例包含多个属性字段,我们要根据...
  • 一个薪水表,salaries简况如下: 建表语句如下: CREATE TABLE `salaries` ( `emp_no` int(11) NOT NULL, `salary` int(11) NOT NULL, `from_date` date NOT NULL, `to_date` date NOT NULL, PRIMARY KEY (`emp_no`...
  • 判断平面上两条直线是否相交

    千次阅读 2013-09-18 22:17:18
    首先引出计算几何学中一个最基本的问题:如何判断向量在的顺时针方向还是逆时针方向? 把p0定为原点,p1的坐标是(x1,y1),p2的坐标是(x2,y2)。向量的叉积(cross product)实际上就是矩阵的行列式:
  • 由使用LeakDialog时遇到的问题而引出的一些分析 前段时间在使用leakDialog检测调用malloc和new所分配的内存泄露时,发现其根本不起作用!这让我百思不得其解!周末有时间研究了一下终于弄清了原因所在。本着分享的...
  • 因此如果有人让你配置负载均衡了,那么你能做到的也就是仅仅“看起来像那么回事”罢了,所谓看起来像的意义就是在用户空间实现一个脚本,每隔一段时间刷新内核的路由表,而路由表中配置多到同一目的地的路由,如此
  • 由Android的fastboot no permissions而引出的Linux特殊权限管理之:SUID、SGID、SBIT
  • (4)原则:一个在指定范围内的数字开始读取,到第一个不在范围内的数字结束%s 可以看成%[]的一个特例 %[^ ](注意^后面有一个空格!) 这样使用sscanf+%[]可以轻松的分析字符串,很多字符串问题便迎刃而解了。 ...
  • 《让你少奋斗10年的工作经验》引出的思考

    千次阅读 多人点赞 2016-06-21 15:55:56
    我的经历看来,大多从事互联网工作的技术工程师,都会将技术的提升放在自身发展的第位,甚至成为种执著。当然也必定会存在考虑自身综合素质的提升和进行刻意训练的技术人员,意识到自身事业发展并不能仅依靠...
  • C 画直线源代码,一个实验课上写的代码,一口气写成的,还不算完美,高手们不要笑话了,...程序主要是在窗体上画直线,其实也不是画,是“”线,鼠标右键在窗体上点两下就会以两点绘制一直线,依次类推,仅供参考。
  • 远程线程注入引出的问题

    千次阅读 多人点赞 2013-05-26 10:31:16
    远程线程注入——相信对Windows底层编程和系统安全熟悉的人并不陌生,其主要核心在于一个Windows API函数CreateRemoteThread,通过它可以在另外一个进程中注入一个线程并执行。在提供便利的同时,正是因为如此,使得...
  • 一个典型的一对多关联中,如果多方是一个数量巨大的集合,那么几乎在任何情况下,我们都不能直接加载这个集合到内存中,进而也就不会有基于这个集合的任何遍历或过滤操作。这些操作本应是对象模型的一些基本行为...
  • 1、问题: malloc分配的内存空间是连续的吗 1、linux内核管理内存空间的分配,所有程序对内存空间的申请和其他操作,最终都会交给内核来管理。...3、linux将所有的内存都以页为单位进行划分,通常每页是4K
  • 什么是单登录(SSO)单登录主要用于多系统集成,即在多个系统中,用户只需要到一个中央服务器登录一次即可访问这些系统中的任何一个,无须多次登录。单登录(Single Sign On),简称为 SSO,是目前比较流行的...
  •  共享池是内存结构中SGA(系统全局区)的部分,包含了:库缓冲区、数据字典缓冲区、服务器结果缓冲区、预留池,也是着四区组成了共享池,这四区的功能就是共享池的功能。  库缓冲区  共享SQL区:存放执行...
  • 笔者准备过程中的总结,是通过填空题,简答题等等总结出来的 如有不足,还望大佬们指教 A14运算器 和 控制器 又称为中央处理器(CPU)。...读写存储器RAM 指可以随机地、个别地对任意一个存储单元进行读写的存.
  • 注意,bitmap 对于这个频繁变更的表不是一个好选择,鉴于我们的判断,boyfriend = 'no' 的记录数极少,那么关于 age + boyfriend 的复合索引就能快速的找到记录,如果你是乐观主义者,就加个 rownum 的限制,如果你...
  • 前面我们讲到的都是线性可分的样本集,即可以用一线(或超平面)将样本集划分成类,但是在处理非线性问题,即不可用一直线划分的样本集(如下图)时,如果我们想要严格的找到一个超平面分类下图中种样本,...
  • 此博文一共有两个配套的麻雀虽小但五脏俱全的示例程序,其经过浅墨详细注释过的代码都在文中贴出,且文章最后提供了综合示例程序的下载。 依然是先看看程序运行截图: 、引言:关于兴趣(interest ...
  • OpenCV目标跟踪()-寻找角

    千次阅读 2015-04-02 22:20:26
    天在看OpenCV中的跟踪与运动这一块,跟踪和运动是一个很大的课题,里面的涉及的内容有很多,按着自己的学习进度上,简要的做个总结。 在我们处理一段视频而非某张静止的图片时,我们往往会去关注画面中的一个...
  • 无论是在CPU外部接总线的设备还是...但是x86比较特殊,x86对于设备有独立的端口地址空间,CPU核需要引出额外的地址线来连接片内设备(和访问内存所用的地址线不同),访问设备寄存器时用特殊的in/out指令(汇编),而不是和访问
  • web版本的项目中有这样一个功能,动态添加联系人,并且可以修改,如下图:    可以由上面四图明显可以看出web上面的联系人功能,最多可以有七个列表,每个列表选择好了以后,又可以修改,如d图。前面选择好的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 45,760
精华内容 18,304
关键字:

从一个点引出两条