MySQL 中一条语句是如何执行的

MySQL的基本架构示意图:
0d2070e8f84c4801adbfa03bda1f98d9
MySQL的逻辑架构
Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖大多数的核心服务功能,以及所有的内置函数;跨存储引擎的功能都在Server层实现,如存储过程、触发器、视图等;
存储引擎层负责数据的存储和提取。插件式的架构,支持InnoDB、MyISAM、Memory等多个存储引擎。

连接器

负责跟客户端建立连接、获取权限、维持和管理连接;
一个用户成功建立连接后,即使用户的权限做了修改,也不会影响已存在的连接的权限,只有新建立的连接才会使用新的权限设置;
连接完成后,如果没有后去操作,这个连接就处于空闲状态,show processlist命令中,Command列显示为sleep的这一行就是对应的那个空闲连接;
客户端如果长时间没有操作,连接器将会断开该客户端的连接,由wait_timeout参数进行控制,默认为8Hours;
连接断开后,如果客户端发送请求,将会受到Lost connection to MySQL server during query的提示;
使用长连接与MySQL建立连接后,有些时候,MySQL占用内存会涨的很快,因为MySQL在执行过程中临时使用的内存是管理在连接对象里面的,这些资源在断开连接的时候才会释放,如果长连接累计下载,导致内存占用太大,被系统强行kill掉(OOM),从现象看就是MySQL异常重启了,解决方案:
1、定期断开长连接;
2、如果使用MySQL5.7+ 版本,可以在执行比较大的操作后,通过执行mysql_reset_connection来重新初始化连接资源;

查询缓存

连接建立完成后,可以执行select 语句了。
MySQL 收到一个查询请求后,会先到查询缓存中检查之前是否执行过该条SQL语句,之前执行过的SQL语句及其结果可能会议key-value对的形式,被直接缓存在内存中。如果SQL语句命中缓存的key,则value会直接返回给客户端。

但大多数情况下,不建议使用查询缓存

查询缓存失效非常频繁,只要对一个表的更新,这个表上所有的查询缓存都会清空。对于工薪压力很大的数据库来说,查询缓存命中率会非常低;
将参数query_cache_type设置为DEMAND,这样对于默认SQL 语句都不使用查询缓存,而对于确定使用查询缓存的语句,可以使用SQL_CACHE显式指定:
select SQL_CACHE * from table where id = 1
注:MySQL8.0已经取消查询缓存这个功能模块;

分析器

分析器先做词法分析,对语句进行识别,以select SQL_CACHE * from table where id = 1为例:
从输入的 select 关键词识别出是个查询语句;把字符串table识别为表名table;把字符串id识别为列id;
然后进行语法分析,判断输入的语句是否满足MySQL语法;
一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接“use near”的内容。

优化器

在表里面有多个索引的时候,决定使用哪个索引;或在一个语句有多表关联(join)的时候,决定各个表的连接顺序,比如:
select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
1、既可以先从t1取出c=10的记录ID值,在根据ID关联到t2,再判断t2里面d的值是否等于20;
2、也可以先从t2取出d=20的记录的id,在根据id关联到t1,判断t1里c的值是否等于10;
两种方法执行逻辑结果一样,但是执行效率会有不同,优化器的左右就是决定使用哪一个方案;

执行器

开始执行的时候,会先判断这个表有没有查询权限,如果没有则会报错

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

假如表T中,ID字段没有索引,那么执行器执行流程如下:
1、调用InnoDB引擎接口获取表的第一行,判断ID是否是10,如果不是则跳过,如果是则将这行存在结果集中;
2、调用引擎接口获取“下一行”,重复判断逻辑,直到取到这个表的最后一行;
3、执行器将上述便利过程中满足条件的行组成的结果集返回给客户端;

对于有索引的表,执行逻辑也差不多;

数据库的慢查询日志中rows_examined字段表示这个语句执行过程中扫描了所烧行,这个值就是在执行器每次调用引擎获取数据的时候累加的;

2015款 MacBook Pro 更换脚垫

自从买了这台15款的 Macbook Pro 之后,就成为主力的生产力工具,当祖宗一样供着,装在背包里背着上下班,保修快到期前又购买了 Apple Care Protect Plan。上个月上班刚到公司,从包里取出这位“祖宗”的时候,忽然发现这位“祖宗”左手手托下方的脚垫脱落了。
WechatIMG148

随后百度了一番,找到苹果官方给的一个指导说明:「MacBook Pro:底壳脚垫更换 DIY 说明」,其中有一段提到可以进行免费更换:WX20180712-150519@2x
遂预约了第二天的「天才吧」准备进行维修。

第二天来到直营店天才吧,说明情况后,工作人员告知:脚垫更换只针对与2013款及以前的 Macbook Pro,而2015款及以后的就没有相关的配件及更换服务了,但是为了帮助我解决我的问题,他可以帮我试着用胶水粘一下,然后就把电脑拿到工作区去粘脚垫了。再拿回来时,脚垫已经粘好了,但是感觉这位工作人员粘的太随意了,脚垫突出来很多,而且很硬,回家后把他们粘的脚垫扣了下来,淘宝个脚垫自己粘!

声明:仅用于 A1502 款 13'Macbook Pro with retina,其他机型仅供参考,另自行拆机可能会失去官方保修,请谨慎对待,此教程不对更换过程中出现的任何问题以及造成的任何后果负责,感谢阅读此声明。

0、准备工作

1)P5 Pentalobe 螺丝刀 x 1
2)平头镊子 x 1
3)电吹风 x 1
4)新的脚垫 (淘宝购买,10元4个包邮)

1、拆卸D壳

(可以参考 iFixit 更换底壳 相关教程进行拆卸。)

1)使用 P5 螺丝刀将 D 壳上的螺丝以此拆下,并将 D 壳翘起;
WechatIMG147

WechatIMG140

需要注意,散热孔附近的两颗螺丝要比其他位置的螺丝短一些:
WechatIMG132

123

2)用电吹风吹一下,将脚垫位置残胶去除干净:WechatIMG149

然后将新的脚垫粘好即可
WechatIMG135

WechatIMG133

注意:有些脚垫可能需要处理一下D壳内部突进去的那个很小的固定柱,一般拿个美工刀用打火机加热一下然后从内侧烫一下固定柱即可。
WechatIMG134

3)将D壳盖回机身,螺丝拧回(注意散热器附近的那两颗较短的螺丝),大功告成!

PS:更换完这个脚垫,过了大约一个月的时间,右手手托的脚垫也GG了,WTF?脚垫也成了消耗品了? 诶,就想问一句 #苹果今天倒闭了么# ?

Laravel route() 方法生成 HTTPS 链接

最近在使用 Laravel 的队列发送邮件,由于邮件中需要附上网站的链接地址,所以使用了 route() 方法来进行渲染。邮件可以正常发出,但是却发现本来是 HTTPS 的链接地址,在邮件中却成为了 HTTP,影响展示效果,于是搜索了一下,找到如下几种解决方法:

1、强制 Laravel 使用 HTTPS

Providers\AppServiceProvider (或者创建其他的 ServiceProvider 并注册) 的 Boot 方法中添加:

\URL::forceScheme('https')

如果是 Laravel 5.3 以下版本,则需要使用:

\URL::forceSchema('https');

2、定义路由时制定 HTTPS

Route::get('/', ['uses' => 'TestController@index', 'https'])->name('index')

PHP 引用变量总结

变量引用对比赋值操作

赋值操作:
// 定义一个变量
$a = range(0, 1000);  // 在内存中申请空间来存储变量a对应的值
var_dump(memory_get_usage());

// 定义变量b,将a变量的值赋值给b
$b = $a;
// 由于PHP的COW,(Copy On Write,只有在变量发生“写”操作之后,系统才会申请新的内存空间)所以,此时,变量a与变量b会指向同一个内存地址。
var_dump(memory_get_usage());

// 对a进行修改
$a = range(0, 1000);
// 变量a发生了“写”操作,系统将在内存中申请新的空间,保存新的值,同时变量a指向原内存地址的指针将会移除,并指向新的内存地址。
var_dump(memory_get_usage());
引用变量

PHP的引用变量定义符为 &
该操作将会将原有变量的指向的内存地址传递给新的变量。

如:

$a = range(0, 1000);   //在内存中开辟空间用于保存 $a 对应的值
$b = &$a;              //将 $a 对应的值在内存中的地址空间传递给 $b , 此时 $a 与 $b 共用同一块内存空间。此时,如果对 $a 或者 $b 进行修改,则 $a 与 $b 的值将同时修改。

两者区别

引用变量传递了内存地址,如果有一个变量的值发生修改,那么两个变量的值都会发生变化;
变量赋值变量修改只会修改发生变化的那个变量的值;在未发生修改之前,与引用变量一样,两者都会指向同一个内存地址

zval变量容器变化

赋值操作
// zval变量容器
$a = range(0, 3);
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=1, is_ref=0)=array(4) {   // refcount=1:引用计数为1,只有变量a;is_ref=0:是否引用变量:false
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/
// 定义变量b,把a的值赋值给b
$b = $a;
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=2, is_ref=0)=array(4) {   // refcount=2:引用次数为2,变量a与变量b同时指向a对应的内存空间;is_ref=0:是否引用变量:false
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/
// 修改a
$a = range(0, 3);
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=1, is_ref=0)=array(4) {   // refcount=1:引用计数为1,由于COW原则,变量a指向了新的内存地址,所以只有变量a;is_ref=0:是否引用变量:false
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/

引用变量

$a = range(0, 3);
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=1, is_ref=0)=array(4) {   // refcount=1:引用计数为1,只有变量a;is_ref=0:是否引用变量:false
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/
// 引用变量
$b = &$a;
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=2, is_ref=1)=array(4) {   // refcount=2:引用计数为2,变量a与变量b同时指向a对应的内存空间;is_ref=1:是否引用变量:true
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/
// 修改变量a
$a = range(0, 3);
xdebug_debug_zval('a');
/***
  * 输出结果
  * a: (refcount=2, is_ref=1)=array(4) {   // refcount=2:引用计数为2,变量a与变量b同时指向a对应的内存空间;is_ref=1:是否引用变量:true;并未受到修改操作的影响
  *     [0] =>(refcount=0, is_ref=0)=int(0)
  *     [1] =>(refcount=0, is_ref=0)=int(1)
  *     [2] =>(refcount=0, is_ref=0)=int(2)
  *     [3] =>(refcount=0, is_ref=0)=int(3)
  * }
  ***/

unset操作

unset操作仅会取消引用,而不会销毁内存空间.

$a = 1;
$b = &$a;
unset($b);  // 此时只取消了变量b的引用,而不会销毁内存空间,变量a不受影响
echo $a;

对象操作

对象本身就是引用传递!!!!如果需要复制,参考 clone

class Person
{
    public $name = "zhangsan";
}

$p1 = new Person;
xdebug_debug_zval('p1');
/***
  * p1: (refcount=1, is_ref=0)=class Person#1 (1) {
  *     public $name =>(refcount=2, is_ref=0)=string(8) "zhangsan"
  * }
 ***/
$p2 = $p1;
xdebug_debug_zval('p1');
/***
  * p1: (refcount=2, is_ref=0)=class Person#1 (1) {
  *     public $name =>(refcount=2, is_ref=0)=string(8) "zhangsan"
  * }
 ***/
$p2->name = "lisi";
xdebug_debug_zval('p1');
/***
  * p1: (refcount=2, is_ref=0)=class Person#1 (1) {
  *     public $name =>(refcount=2, is_ref=0)=string(8) "lisi"
  * }
 ***/

Example

$data = ['a', 'b', 'c'];

foreach ($data as $key=>$val)
{
    $val = &$data[$key];
}

var_dump($data);  // 输出['b', 'c', 'c']
/***
  * 分析:
  * foreach循环中,
  * 第一次循环:
  *     $data as $key => $val: $val = $data[0] (即'a'), $key = 0, 进入循环体;
  *     由于是引用变量操作,所以 $val 与 $data[0] 均会指向 ‘a’,第一次循环结束;
  * 第二次循环:
  *     $data as $key => $val: $val = $data[1] (即'b'),由于上一步循环中,$val 与 $data[0]是指向的同一个内存地址,且为引用变量,所以,此时$data[0]值则变为了‘b’,进入循环体
  *     由于是引用变量操作,所以 $val 与 $data[1] 均会指向 ‘b’,第二次循环结束;
  * 第三次循环:
  *     $data as $key => $val: $val = $data[2] (即'c'),由于上一步循环中,$val 与 $data[1]是指向的同一个内存地址,且为引用变量,所以,此时$data[1]值则变为了‘c’,进入循环体
  *     由于是引用变量操作,所以 $val 与 $data[1] 均会指向 ‘c’,第三次循环结束;
  * 循环结束后,$data[0] = 'b',$data[1] = 'C',$data[2] = 'c',
  * 所以最后输出结果为['b', 'c', 'c']
 ***/

Mac 系统下安装树莓派(Respberrypi)

准备

8GB SD卡一张 (*)
树莓派 (*)
HDMI连接线 / HDMI转接线
显示器
键盘

1、镜像及工具下载

1.1 镜像选择及下载

树莓派镜像下载地址:https://www.raspberrypi.org/downloads/。目前官方提供了 NOOBS 及基于 Debian 的Raspbian 两个发行版系统,相应的官方说明文档如下:

NOOBS

NOOBS is an easy operating system installer which contains Raspbian. It also provides a selection of alternative operating systems which are then downloaded from the internet and installed.

NOOBS Lite contains the same operating system installer without Raspbian pre-loaded. It provides the same operating system selection menu allowing Raspbian and other images to be downloaded and installed.

Raspbian

Raspbian is the Foundation’s official supported operating system. You can install it with NOOBS or download the image below and follow our installation guide.

Raspbian comes pre-installed with plenty of software for education, programming and general use. It has Python, Scratch, Sonic Pi, Java, Mathematica and more.

The Raspbian with Desktop image contained in the ZIP archive is over 4GB in size, which means that these archives use features which are not supported by older unzip tools on some platforms. If you find that the download appears to be corrupt or the file is not unzipping correctly, please try using 7Zip (Windows) or The Unarchiver (Macintosh). Both are free of charge and have been tested to unzip the image correctly.

除此之外,在 Download 页面,Raspberrypi 还提供了其他第三方系统镜像的下载地址:

第三方下载

大家可以根据自己的实际需要来选择镜像下载!下载完成后,记得进行校验。后续将以 RASPBIAN STRETCH LITE 为例进行安装。

1.2 刻录工具下载

Mac 下,使用了 Etcher 进行刻录,下载地址:https://etcher.io/

Etcher is a graphical SD card writing tool that works on Mac OS, Linux and Windows, and is the easiest option for most users. Etcher also supports writing images directly from the zip file, without any unzipping required. To write your image with Etcher:

下载后,直接拖动安装即可。

2、镜像刻录

将下载后的镜像解压,并将 SD 卡插入到电脑上,打开下载的 Etcher,按照下图所示,把系统镜像刻录到 SD 卡上

screenshot

刻录完成后,取出SD卡,插入到树莓派开发版,并连接网络、HDMI 显示器以及USB 键盘,上电启动树莓派。

3、其他

官方镜像,默认没有启用 sshd 服务,因此没有办法使用终端工具连接到开发板上,因此需要插入键盘及显示器,启动 SSH 服务。

sudo systemctl start sshd
sudo systemctl enable sshd

启动之后,开发板只需要连接电源及网线,就可以通过终端工具 + IP 进行远程访问与配置了