0%

关于本人

关于博客

  • why-how-what,理念-逻辑-知识,这就是知识的黄金圆环。

  • 博客建立只是为了监督个人学习,记录生活点滴,个人水平有限欢迎斧正。

  • 博客章节地址可能会发生变化,因为我也是在不断的归纳整理知识内容。

  • 2018年4月开始断断续续写内容,加油。

Read more »

最近在编写爬虫程序,这里简单的做下笔记。

阅读注意事项

  1. 需要一台服务器(阿里ESC)这种,分配的公网ip。
  2. 这里使用的yum安装,所以环境配置都创建好了,不需要在手动修改。

准备工作

安装Java、maven、git、selenium运行环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 使用的机器是centos7.4
# 系统环境一个一个来安装。
# 1.检查是否安装jdk
-> yum install java-1.8.0-openjdk-devel.x86_64
-> java -version
openjdk version "1.8.0_262"
OpenJDK Runtime Environment (build 1.8.0_262-b10)
OpenJDK 64-Bit Server VM (build 25.262-b10, mixed mode)
-> yum install maven
-> mvn -version
Java version: 1.8.0_262, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.262.b10-0.el7_8.x86_64/jre
Default locale: zh_CN, platform encoding: UTF-8
OS name: "linux", version: "3.10.0-862.el7.x86_64", arch: "amd64", family: "unix"
-> yum install git
git version 1.8.3.1
# 查看是否生成过证书
-> ls -al ~/.ssh
# 这里配置下ssh公钥方便git拉去代码
-> ssh-keygen -t rsa -C “you email@gamil.com”
# 查看证书
-> cat ~/.ssh/id_rsa.pub
# 开始配置 selenium 运行环境
-> wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
-> yum localinstall google-chrome-stable_current_x86_64.rpm
# 检查版本
-> google-chrome --version
Google Chrome 85.0.4183.83
# 安装 chromedriver
-> yum install chromedriver
-> chromedriver -version
ChromeDriver 84.0.4147.89
# 这里注意 chromedriver 可能与chrome的版本不一致,去官方网站下载后解压复制到/usr/bin中
  • 注意 google-chrome 和 chromedriver版本是需要对应的具体可以到官方查看。
  • 在centos上运行需要配置下关键参数
1
2
3
4
5
6
7
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--headless");
chromeOptions.addArguments("--disable-gpu"); // 不配置一定报错
chromeOptions.addArguments("--no-sandbox");
// 创建无界面浏览器对象
chromeOptions.setHeadless(true);
return new ChromeDriver(chromeOptions);
  • 启动代码的需要指定环境变量
1
2
System.setProperty("webdriver.chrome.chromedriver", "/usr/bin/chromedriver");
# 具体可以通过 whereis chromedriver查看。

参考文献

CentOS使用yum安装jdk

最近开始用mac来开发。记录下环境安装过程方便以后快速恢复熟悉的开发环境。

这里提前修改一下控制台前缀

1
2
3
4
5
[dev mc]$ sudo vim /etc/bashrc
#修改以下内容
PS1='\h:\W \u\$ '
#修改成
PS1='[dev \u]$ '

好用的工具

  • pages 同事推荐的软件暂时用来写周报。
  • brew 这是一个神器,和linux 上的yum 类似的工具。
  • sublime text 我之前在windows上都是使用notepad++。上App Store上没找到,该用这个了。

pages

  • 直接在app store安装就好了

brew

  • 安装过程使用终端安装。
1
2
#  这里并没有换行,复制的时候请注意。
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  • 基本操作方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 更新
brew update
# 安装软件
brew install xxx(软件名)

#常见命令整理如下,以下命令都可带上 cask 参数:

brew search name:联网搜索软件是否存在 brew 中
brew install name:安装软件
brew upgrade name:更新软件
brew uninstall name:卸载软件
brew reinstall name:重新安装软件
brew info name:查看软件安装地址
brew cleanup:清理缓存等
brew doctor:查看建议,例如升级等
Read more »

本章是整理知识内容,为强化知识长期更新。

Mysql基础架构图

mysql基础架构图

简单介绍上图相关模块功能

  • 连接器:管理连接,权限验证

  • 查询缓存:命中缓存则直接返回结果

  • 语法解析:词法分析,语法分析

  • 查询优化:执行计划生成,索引选择

  • 执行器:操作引擎,返回结果

  • 存储引擎:存储数据,提供读写结构。

连接器

我们需要连接到mysql服务端才能进行各种操作。

1
2
# 使用密码登录到时候我们会使用这种格式。
mysql -h$ip -P$port -u$user -p

执行上述命令会提示输入密码。输入完密码后就进入了mysql。

  • 如果用户名或者密码输入错误,会提示一个Access denied for user到错误,然后客户端程序结束执行。

  • 如果用户名和密码认证通过,连接器会去权限表中查询该用户拥有的所有权限。此后该连接里面的权限逻辑判断都会依赖认证成功时候读取到的权限。

因此当修改用户的权限之后,如果被修改用户的在登录状态是不会修改已经连接的权限。需要让被修改权限用户从新登录。

  • 实际上连接的方式有多种,上面通过密码的方式只是其中的一种方式。当客户端与服务端mysql进程进程建立连接,服务器的进程就会创建一个单独的线程来专门处理与这个客户端的交互,当该客户断开与服务端连接时,服务端并不会马上销毁对应的交互线程,而是缓存起来。当另一个新的客户端连接进来的时候,在把这个缓存的线程分配给新的客户端。这样就避免的频繁创建和修改线程,节省系统的开销。但是连接线程多了也会影响服务端,所以也有默认的参数限制客户端的连接数量。
1
2
# 这里是查询服务端存在的连接。
SHOW PROCESSLIST;

我使用的阿里云的数据库,这里展示下结果集。这里需要注意,登录的账号需要有PROCESS的权限否则只能看到自己的连接信息(线程),我这里采用的root所以看到全部到。

连接完成后如果没有执行后续操作则 Command会显示Sleep的状态,长时间Sleep会导致连接自动断开。默认的时间是8小时,也就是参数 wait_timeout。当客户端连接断开后,若客户端再次发出请求就会提示一个

Lost connection to MySQL server during query,此时就只能重新建立连接了。

半双工
  • MySQL客户端/服务端通信协议是“半双工”的:在任一时刻,要么是服务器向客户端发送数据,要么是客户端向服务器发送数据,这两个动作不能同时发生。一旦一端开始发送消息,另一端要接收完整个消息才能响应它,所以我们无法也无须将一个消息切成小块独立发送,也没有办法进行流量控制。
  • 服务器响应给用户的数据通常会很多,由多个数据包组成。但是当服务器响应客户端请求时,客户端必须完整的接收整个返回结果,而不能简单的只取前面几条结果,然后让服务器停止发送。因而在实际开发中,尽量保持查询简单且只返回必需的数据,减小通信间数据包的大小和数量是一个非常好的习惯,这也是查询中尽量避免使用SELECT *以及加上LIMIT限制的原因之一。
长连接与短连接
  • 在数据库里面,长连接是指数据库建立连接,如果客户端有持续的请求则使用同一个连接,短连接是指每次执行几次短的查询就断开连接,下次查询再次创建连接。由于创建的过程复杂,一般来说尽量减少创建连接的动作,尽可能使用长连接。特别是在开发的时候出现大伙突然连接不上测试库,此时就检查下连接是不是太多了。因为连接多了会导致内存消耗特别快。

    • mysql的执行过程中临时使用的内存管理是在连接线程对象里面,服务端断开的时候才会释放,为了避免内存消耗过大,每次执行较大的操作之后,可以通过mysql_reset_connection来重新初始化连接资源。这个操作是不需要重新做权限,只是恢复到创建的初始状态。
  • MySQL mysql_reset_connection 官方文档

查询缓存

一个不被建议使用的功能,在新版的8.0中已经被删除了。总结就是弊大于利。

  • 建立完成后,假设执行一次SELECT语句,执行逻辑就会到第二部查询缓存。mysql拿到一个查询之后,会先到缓存中寻找释放有完全对应的,因为之前执行的查询语句和结果集会直接被缓存起来,以Keys-value的形式。keys是查询语句-value是查询结果集。如果能命中这个key则直接返回value。咋一看挺有用的,但实际上查询缓存的实效的太频繁。并且keys的命中条件太不聪明了,如果两次查询语句在任何的字符上存在不通(空格、大小写)都不会缓存命中。为什么会频繁失效,因为如果对查询语句中的表进行更新,就会导致缓存失效。比如INSERT、UPDATE 、DELETE 、TRUNCATE TABLE 、ALTER TABLE 等等就会导致缓存失效。

语法解析

如果没有命中查询缓存,就要开始执行sql语句了。mysql需要做什么,因此需要对sql进行解析。MySQL通过关键字将SQL语句进行解析,并生成一颗对应的解析树。这个过程解析器主要通过语法规则来验证和解析。比如SQL中是否使用了错误的关键字或者关键字的顺序是否正确等等。预处理则会根据MySQL规则进一步检查解析树是否合法。比如检查要查询的数据表和数据列是否存在等等。

  • 本质上发送过来的是一个文本信息,这里就需要对该文本信息进行编译,涉及到词法解析语法解析语义解析等阶段。需要注意的是,这里会对sql语句进行一些检查,比如设计的相关表、表字段。这里检查通过的才会进入查询优化模块。

查询优化

通过之前的语法解析,基本可以判定语法树是合法的,此时mysql就知道文本内容要做什么。

  • mysql会对查询语句进行优化,优化的结果就是生成一个执行计划,这个计划会表明使用了那些查询索引,表之间的连接是什么样子的。有时候出现多种执行方式,只是效率不通,优化器会决定使用哪一个执行方案,这个时候就涉及到EXPLAIN语句,该语句可以查看sql到执行计划,这将涉及到查询优化。
  • MySQL的查询优化器是一个非常复杂的部件,它使用了非常多的优化策略来生成一个最优的执行计划:
    • 重新定义表的关联顺序(多张表关联查询时,并不一定按照SQL中指定的顺序进行,但有一些技巧可以指定关联顺序)
    • 优化函数
    • 提前终止查询(比如:使用Limit时,查找到满足数量的结果集后会立即终止查询)
    • 优化排序(在老版本MySQL会使用两次传输排序,即先读取行指针和需要排序的字段在内存中对其排序,然后再根据排序结果去读取数据行,而新版本采用的是单次传输排序,也就是一次读取所有的数据行,然后根据给定的列排序。对于I/O密集型应用,效率会高很多。

执行器

进入到执行sql到环节。

  • 通过之前到查询优化,进入执行阶段,此时会先判断该连接用户是否有相关表的权限,如果没有会返回权限错误。如果有权限就会打开表,执行器会根据表定义的引擎去使用这个引擎提供的接口完成流程。

存储引擎

关于存储引擎,这里明确提醒。不是三种也不是四种。而是多种因为随时可能出现新的引擎。

  • 我们常用的就是InnoDBMyISAM,其它的不常见就不做过多介绍。mysql现在默认的引擎是InnoDB所以主要也是了解InnoDB
1
2
# 查看当前服务支持的存储引擎
SHOW ENGINES;

Read more »

本章是整理知识内容,为强化知识长期更新。

Redis 简介

Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key - value 数据库

  • Redis 与 其他 key - value 缓存产品有以下三个特点:
    • Redis 支持数据持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
    • Redis 不仅仅支持简单的 key - value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储
    • Redis 支持数据的备份,即 master - slave 模式的数据备份。
  • Redis是内存数据库,所有操作都在内存上完成,内存的访问速度本身就很快,读的速度是 110000 次 /s, 写的速度是 81000 次 /s。另一方面是因为它的数据结构。键值对是按一定的数据结构来组织的,操作键值对最终就是对数据结构进行增删改查操作,所以高效的数据结构是 Redis 快速处理数据的基础。

Redis单线程

  • 我们通常说,Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。Redis 的单线程设计机制以及多路复用机制
  • Linux 中的 IO 多路复用机制
    • Linux 中的 IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

Redis单线程处理IO请求性能瓶颈

  • 任意一个请求在server中一旦发生耗时,都会影响整个server的性能,也就是说后面的请求都要等前面这个耗时请求处理完成,自己才能被处理到。耗时的操作包括以下几种:
    • 操作bigkey:写入一个bigkey在分配内存时需要消耗更多的时间,同样,删除bigkey释放内存同样会产生耗时;
    • 使用复杂度过高的命令:例如SORT/SUNION/ZUNIONSTORE,或者O(N)命令,但是N很大,例如lrange key 0 -1一次查询全量数据;
    • 大量key集中过期:Redis的过期机制也是在主线程中执行的,大量key集中过期会导致处理一个请求时,耗时都在删除过期key,耗时变长;
    • 淘汰策略:淘汰策略也是在主线程执行的,当内存超过Redis内存上限后,每次写入都需要淘汰一些key,也会造成耗时变长;
    • AOF刷盘开启always机制:每次写入都需要把这个操作刷到磁盘,写磁盘的速度远比写内存慢,会拖慢Redis的性能;
    • 主从全量同步生成RDB:虽然采用fork子进程生成数据快照,但fork这一瞬间也是会阻塞整个线程的,实例越大,阻塞时间越久;
  • 并发量非常大时,单线程读写客户端IO数据存在性能瓶颈,虽然采用IO多路复用机制,但是读写客户端数据依旧是同步IO,只能单线程依次读取客户端的数据,无法利用到CPU多核。
  • Redis在4.0推出了lazy-free机制,把bigkey释放内存的耗时操作放在了异步线程中执行,降低对主线程的影响。
  • Redis在6.0推出了多线程,可以在高并发场景下利用CPU多核多线程读写客户端数据,进一步提升server性能,当然,只是针对客户端的读写是并行的,每个命令的真正操作依旧是单线程的。

Redis持久化机制

  • Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
    • 实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程内存释放。
  • RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。)
  • AOF:Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。

AOF日志

  • 写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存,然后才记录日志。
  • AOF 也有两个潜在的风险
    • 如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数据就有丢失的风险。如果此时 Redis 是用作缓存,还可以从后端数据库重新读入数据进行恢复,但是,如果 Redis 是直接用作数据库的话,此时,因为命令没有记入日志,所以就无法用日志进行恢复了。
    • AOF 虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因为,AOF 日志也是在主线程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了。
  • AOF三种写回策略
    • Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
      • 同步写回可以做到基本不丢数据,但是它在每一个写命令后都有一个慢速的落盘操作,不可避免地会影响主线程性能。
    • Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
      • 每秒写回采用一秒写回一次的频率,避免了“同步写回”的性能开销,虽然减少了对系统性能的影响,但是如果发生宕机,上一秒内未落盘的命令操作仍然会丢失。所以,这只能算是,在避免影响主线程性能和避免数据丢失两者间取了个折中。
    • No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
      • 操作系统控制的写回在写完缓冲区后,就可以继续执行后续的命令,但是落盘的时机已经不在 Redis 手中了,只要 AOF 记录没有写回磁盘,一旦宕机对应的数据就丢失了。

RDB快照

  • Redis 的数据都在内存中,为了提供所有数据的可靠性保证,它执行的是全量快照,也就是说,把内存中的所有数据都记录到磁盘中。全量数据越多,RDB 文件就越大,往磁盘上写数据的时间开销就越大。
  • 对于 Redis 而言,它的单线程模型就决定了,我们要尽量避免所有会阻塞主线程的操作,所以,针对任何操作,我们都会提一个灵魂之问:“它会阻塞主线程吗?”RDB 文件的生成是否会阻塞主线程,这就关系到是否会降低 Redis 的性能。
  • Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。
    • save:在主线程中执行,会导致阻塞;
    • bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
Read more »

本章属于持续学习、长期更修。

数据库的三范式是什么

  • 第一范式:列不可再分

  • 第二范式:行可以唯一区分,主键约束

  • 第三范式:表的非主属性不能依赖与其他表的非主属性 外键约束

  • 且三大范式是一级一级依赖的,第二范式建立在第一范式上,第三范式建立第一第二范式上。

  • 第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。

  • 第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。

  • 第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如 果存在”A → B → C”的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系: 关键字段 → 非关键字段 x → 非关键字段y

说下mysql有几种数据库引擎

不清楚,因为每个版本发布都会新增一些。但是我最熟悉是的innodb。

说下mysql Master slave 的原理

  • :binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中;

  • :io线程——在使用start slave 之后,负责从master上拉取 binlog 内容,放进 自己的relay log中;

  • :sql执行线程——执行relay log中的语句;

MySQL中myisam与innodb的区别

  • InnoDB支持事物,而MyISAM不支持事物
  • InnoDB支持行级锁,而MyISAM支持表级锁
  • InnoDB支持MVCC, 而MyISAM不支持
  • InnoDB支持外键,而MyISAM不支持
  • InnoDB不支持全文索引,而MyISAM支持。

innodb引擎的4大特性

  • 插入缓冲(insert buffer),
  • 二次写(double write),
  • 自适应哈希索引(ahi),
  • 预读(read ahead)
  • 支持事务操作,具有事务 ACID 隔离特性,默认的隔离级别是可重复读(repetable-read)、通过MVCC(并发版本控制)来实现的。能够解决脏读不可重复读的问题。
  • InnoDB 支持外键操作。
  • InnoDB 默认的锁粒度行级锁,并发性能比较好,会发生死锁的情况。
  • 和 MyISAM 一样的是,InnoDB 存储引擎也有 .frm文件存储表结构 定义,但是不同的是,InnoDB 的表数据与索引数据是存储在一起的,都位于 B+ 数的叶子节点上,而 MyISAM 的表数据和索引数据是分开的。
  • InnoDB 有安全的日志文件,这个日志文件用于恢复因数据库崩溃或其他情况导致的数据丢失问题,保证数据的一致性。
  • InnoDB 和 MyISAM 支持的索引类型相同,但具体实现因为文件结构的不同有很大差异。
  • 增删改查性能方面,如果执行大量的增删改操作,推荐使用 InnoDB 存储引擎,它在删除操作时是对行删除,不会重建表。

什么是事务?

  • 事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性。

覆盖索引和回表

  • 覆盖索引指的是在一次查询中,如果一个索引包含或者说覆盖所有需要查询的字段的值,我们就称之为覆盖索引,而不再需要回表查询。而要确定一个查询是否是覆盖索引,我们只需要explain sql语句看Extra的结果是否是“Using index”即可。

MySQL 事务四大特性

一说到 MySQL 事务,你肯定能想起来四大特性:原子性一致性隔离性持久性。什么是 MySQL 中的事务?

事务是一组操作,组成这组操作的各个单元,要不全都成功要不全都失败,这个特性就是事务。

在 MySQL 中,事务是在引擎层实现的,只有使用 innodb 引擎的数据库或表才支持事务。

  • 原子性(Atomicity): 原子性指的就是 MySQL 中的包含事务的操作要么全部成功、要么全部失败回滚,因此事务的操作如果成功就必须要全部应用到数据库,如果操作失败则不能对数据库有任何影响。
  • 一致性(Consistency):一致性指的是一个事务在执行前后其状态一致。比如 A 和 B 加起来的钱一共是 1000 元,那么不管 A 和 B 之间如何转账,转多少次,事务结束后两个用户的钱加起来还得是 1000,这就是事务的一致性。
  • 持久性(Durability): 持久性指的是一旦事务提交,那么发生的改变就是永久性的,即使数据库遇到特殊情况比如故障的时候也不会产生干扰。
  • 隔离性(Isolation):当多个事务同时进行时,就有可能出现脏读(dirty read)不可重复读(non-repeatable read)幻读(phantom read) 的情况,为了解决这些并发问题,提出了隔离性的概念。
    • 重点说下隔离性它们分别是读未提交(read uncommitted)读已提交(read committed)可重复读(repetable read)串行化(serializable)。下面分别来解释一下。
    • 读未提交:读未提交指的是一个事务在提交之前,它所做的修改就能够被其他事务所看到。
    • 读已提交:读已提交指的是一个事务在提交之后,它所做的变更才能够让其他事务看到。
    • 可重复读:可重复读指的是一个事务在执行的过程中,看到的数据是和启动时看到的数据是一致的。未提交的变更对其他事务不可见。
    • 串行化:顾名思义是对于同一行记录,会加写锁会加读锁。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

SQL语句的执行顺序

我们在编写一个查询语句的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT DISTINCT
< select_list >
FROM
< left_table > < join_type >
JOIN < right_table > ON < join_condition >
WHERE
< where_condition >
GROUP BY
< group_by_list >
HAVING
< having_condition >
ORDER BY
< order_by_condition >
LIMIT < limit_number >
  1. FROM 连接
  • 首先,对 SELECT 语句执行查询时,对FROM 关键字两边的表执行连接,会形成笛卡尔积,这时候会产生一个虚表VT1(virtual table)

首先先来解释一下什么是笛卡尔积

现在我们有两个集合 A = {0,1} , B = {2,3,4}

那么,集合 A * B 得到的结果就是

A * B = {(0,2)、(1,2)、(0,3)、(1,3)、(0,4)、(1,4)};

B * A = {(2,0)、{2,1}、{3,0}、{3,1}、{4,0}、(4,1)};

上面 A * B 和 B * A 的结果就可以称为两个集合相乘的 笛卡尔积

我们可以得出结论,A 集合和 B 集合相乘,包含了集合 A 中的元素和集合 B 中元素之和,也就是 A 元素的个数 * B 元素的个数

  1. ON 过滤
  • 然后对 FROM 连接的结果进行 ON 筛选,创建 VT2,把符合记录的条件存在 VT2 中。
  1. JOIN 连接

第三步,如果是 OUTER JOIN(left join、right join) ,那么这一步就将添加外部行,如果是 left join 就把 ON 过滤条件的左表添加进来,如果是 right join ,就把右表添加进来,从而生成新的虚拟表 VT3。

  1. WHERE 过滤
  • 第四步,是执行 WHERE 过滤器,对上一步生产的虚拟表引用 WHERE 筛选,生成虚拟表 VT4。

  • WHERE 和 ON 的区别

    • 如果有外部列,ON 针对过滤的是关联表,主表(保留表)会返回所有的列;
    • 如果没有添加外部列,两者的效果是一样的;
  • 应用

    • 对主表的过滤应该使用 WHERE;
    • 对于关联表,先条件查询后连接则用 ON,先连接后条件查询则用 WHERE;
  1. GROUP BY
  • 根据 group by 字句中的列,会对 VT4 中的记录进行分组操作,产生虚拟机表 VT5。果应用了group by,那么后面的所有步骤都只能得到的 VT5 的列或者是聚合函数(count、sum、avg等)。
  1. HAVING
  • 紧跟着 GROUP BY 字句后面的是 HAVING,使用 HAVING 过滤,会把符合条件的放在 VT6
  1. SELECT
  • 第七步才会执行 SELECT 语句,将 VT6 中的结果按照 SELECT 进行刷选,生成 VT7
  1. DISTINCT
  • 在第八步中,会对 TV7 生成的记录进行去重操作,生成 VT8。事实上如果应用了 group by 子句那么 distinct 是多余的,原因同样在于,分组的时候是将列中唯一的值分成一组,同时只为每一组返回一行记录,那么所以的记录都将是不相同的。
  1. ORDER BY
  • 应用 order by 子句。按照 order_by_condition 排序 VT8,此时返回的一个游标,而不是虚拟表。sql 是基于集合的理论的,集合不会预先对他的行排序,它只是成员的逻辑集合,成员的顺序是无关紧要的。

什么是临时表,何时删除临时表

1
2
3
4
在 MySQL 中,有三种类型的表
一种是`永久表`,永久表就是创建以后用来长期保存数据的表
一种是`临时表`,临时表也有两类,一种是和永久表一样,只保存临时数据,但是能够长久存在的;还有一种是临时创建的,SQL 语句执行完成就会删除。
一种是`虚表`,虚表其实就是`视图`,数据可能会来自多张表的执行结果。
  • MySQL 会在下面这几种情况产生临时表
    • 使用 UNION 查询:UNION 有两种,一种是UNION ,一种是 UNION ALL ,它们都用于联合查询;区别是 使用 UNION 会去掉两个表中的重复数据,相当于对结果集做了一下去重(distinct)。使用 UNION ALL,则不会排重,返回所有的行。使用 UNION 查询会产生临时表。
    • 使用 TEMPTABLE 算法或者是 UNION 查询中的视图。TEMPTABLE 算法是一种创建临时表的算法,它是将结果放置到临时表中,意味这要 MySQL 要先创建好一个临时表,然后将结果放到临时表中去,然后再使用这个临时表进行相应的查询。
    • ORDER BY 和 GROUP BY 的子句不一样时也会产生临时表。
    • DISTINCT 查询并且加上 ORDER BY 时;
    • SQL 用到 SQL_SMALL_RESULT 选项时;如果查询结果比较小的时候,可以加上 SQL_SMALL_RESULT 来优化,产生临时表
    • FROM 中的子查询;
    • EXPLAIN 查看执行计划结果的 Extra 列中,如果使用 Using Temporary 就表示会用到临时表。

介绍下InnoDB存储引擎的B+树索引

  • 每个索引都对应一棵B+树,B+树分为好多层,最下边一层是叶子节点,其余的是内节点。所有用户记录都存储在B+树的叶子节点,所有目录项记录都存储在内节点。
  • InnoDB存储引擎会自动为主键(如果没有它会自动帮我们添加)建立聚簇索引,聚簇索引的叶子节点包含完整的用户记录。
  • 我们可以为自己感兴趣的列建立二级索引,二级索引的叶子节点包含的用户记录由索引列 + 主键组成,所以如果想通过二级索引来查找完整的用户记录的话,需要通过回表操作,也就是在通过二级索引找到主键值之后再到聚簇索引中查找完整的用户记录。
  • B+树中每层节点都是按照索引列值从小到大的顺序排序而组成了双向链表,而且每个页内的记录(不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序而形成了一个单链表。如果是联合索引的话,则页面和记录先按照联合索引前边的列排序,如果该列值相同,再按照联合索引后边的列排序。
  • 通过索引查找记录是从B+树的根节点开始,一层一层向下搜索。由于每个页面都按照索引列的值建立了Page Directory(页目录),所以在这些页面中的查找非常快。
  • 二者SELECT COUNT(*)哪个更快,为什么
    • myisam更快,因为myisam内部维护了一个计数器,可以直接调取。
Read more »

本章属于持续学习、长期更修。

谈谈对Spring理解

  • Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。
  • 主要由以下几个模块组成:
    • Spring Core:核心类库,提供IOC服务
    • Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等)
    • Spring AOP:AOP服务切面编程
    • Spring DAO:对JDBC的抽象,简化了数据访问异常的处理
    • Spring ORM:对现有的ORM框架的支持
    • Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传
    • Spring MVC:提供面向Web应用的Model-View-Controller实现

谈谈对 IoC 和 DI 的理解

  • IoC(Inversion of Control,翻译为“控制反转”)不是一个具体的技术,而是一种设计思想。与传统控制流相比,IoC 会颠倒控制流,在传统的编程中需要开发者自行创建并销毁对象,而在 IoC 中会把这些操作交给框架来处理,这样开发者就不用关注具体的实现细节了,拿来直接用就可以了,这就是控制反转。
  • IoC 很好的体现出了面向对象的设计法则之一——好莱坞法则:“别找我们,我们找你”。即由 IoC 容器帮对象找到相应的依赖对象并注入,而不是由对象主动去找。
  • DI(Dependency Injection,翻译为“依赖注入”)表示组件间的依赖关系交由容器在运行期自动生成,也就是说,由容器动态的将某个依赖关系注入到组件之中,这样就能提升组件的重用频率。通过依赖注入机制,我们只需要通过简单的配置,就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心资源来自哪里、由谁实现等问题。
  • IoC 和 DI 其实是同一个概念从不同角度的描述的,由于控制反转这个概念比较含糊(可能只理解成了容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以 2004 年被开发者尊称为“教父”的 Martin Fowler(世界顶级专家,敏捷开发方法的创始人之一)又给出了一个新的名字“依赖注入”,相对 IoC 而言,“依赖注入”明确描述了“被注入对象依赖 IoC 容器配置依赖对象”。

Spring 中的单例 bean 的线程安全问题了解吗?

  • 单例 bean 存在线程问题,主要是因为当多个线程操作同⼀个对象的时候,对这个对象的⾮静态成员变量的写操作会存在线程安全问题。在类中定义⼀个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的⼀种⽅式)。

Spring框架中都用到了哪些设计模式

  • 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例
  • 单例模式:Bean默认为单例模式
  • 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
  • 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate
  • 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener,Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、spring MVC 中也是⽤到了适配器模式适配 Controller 。

Spring中Autowired和Resource关键字的区别?

  • @Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
  • Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualififier注解一起使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestServiceImpl { 
// 下面两种@Autowired只要使用一种即可
@Autowired
private UserDao userDao; // 用于字段上

@Autowired // 用于属性的方法上
public void setUserDao(UserDao userDao)
{
this.userDao = userDao;
}
}
// @Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允
// 许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结
// 合@Qualifier注解一起使用。如下:
public class TestServiceImpl {
@Autowired
@Qualifier("userDao") private UserDao userDao;
}
  • Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

public class TestServiceImpl {
// 下面两种@Resource只要使用一种即可

// 用于字段上
@Resource(name="userDao")
private UserDao userDao;

// 用于属性的setter方法上
@Resource(name="userDao")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}

Bean注册方式有几种?

  • XML 配置文件的注册方式
1
2
3
4
<bean id="person" class="org.springframework.beans.Person">
   <property name="id" value="1"/>
   <property name="name" value="Java"/>
</bean>
  • Java 注解的注册方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 可以使用 @Component 注解方式来注册 Bean
@Component
public class Person {
   private Integer id;
   private String name
   // 忽略其他方法
}

// @Bean 注解方式来注册 Bean
@Configuration
public class Person {
   @Bean
   public Person  person(){
      return new Person();
   }
   // 忽略其他方法
}
  • Java API 的注册方式
1
2
3
4
5
6
7
8
9
10
11
12
// 使用 BeanDefinitionRegistry.registerBeanDefinition() 方法的方式注册 Bean	
public class CustomBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
RootBeanDefinition personBean = new RootBeanDefinition(Person.class);
// 新增 Bean
registry.registerBeanDefinition("person", personBean);
}
}
Read more »

Selenium

Selenium是最广泛使用的开源Web UI(用户界面)自动化测试套件之一。它最初由Jason Huggins于2004年开发,作为Thought Works的内部工具。Selenium支持跨不同浏览器,平台和编程语言的自动化。

  • Selenium可以轻松部署在Windows,Linux,Solaris和Macintosh等平台上。此外,它支持iOS(iOS,Windows Mobile和Android)等移动应用程序的OS(操作系统)。

  • Selenium通过使用特定于每种语言的驱动程序支持各种编程语言.Selenium支持的语言包括C#,Java,Perl,PHP,Python和Ruby。目前,Selenium Web驱动程序最受Java和C#欢迎。Selenium测试脚本可以使用任何支持的编程语言进行编码,并且可以直接在大多数现代Web浏览器中运行。Selenium支持的浏览器包括Internet Explorer,Mozilla Firefox,Google Chrome和Safari。

Selenium WebDriver

Selenium WebDriver是Selenium RC的继承者。Selenium WebDriver接受命令(在Selenese中发送,或通过客户端API)并将它们发送到浏览器。这是通过特定于浏览器的浏览器驱动程序实现的,该驱动程序将命令发送到浏览器并检索结果。大多数浏览器驱动程序实际上启动并访问浏览器应用程序。

  • Selenium WebDriver是Selenium Tool套件中最重要的组件。最新版本“Selenium 2.0”与WebDriver API集成,提供更简单,更简洁的编程接口。

环境配置

这里选用的是Google Chrome测试套件。

  • 注意,使用jdk8。

  • 下载ChromeDriver

    • 由三个独立的部分组成。有浏览器本身(“chrome”),Selenium项目提供的语言绑定(“驱动程序”)和从Chromium项目下载的可执行文件,它充当“chrome”和“driver”之间的桥梁。此可执行文件称为“chromedriver”,但我们将尝试将其称为此页面中的“服务器”以减少混淆。
    • 根据电脑安装的chrome版本选择合适的版本下载地址

快速安装

1
2
3
4
# Mac users with Homebrew installed: 
brew tap homebrew/cask && brew cask install chromedriver
# Debian based Linux distros:
sudo apt-get install chromium-chromedriver

快速更新

1
2
# Mac users with Homebrew reInstall 
brew cask reinstall chromedriver
Read more »

​ 衔接上一篇爬虫入门。为了更好爬输出,有必要Xpath用法有一定了解。

​ 了解Xpath之前先了解下XML、HTML。如果完全不知道是什么,建议系统学习HTML、XML下阅读下文。

XML

可扩展标记语言(英语:Extensible Markup Language,简称:XML),是一种标记语言。标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等。如何定义这些标记,既可以选择国际通用的标记语言,比如HTML,也可以使用像XML这样由相关人士自由决定的标记语言,这就是语言的可扩展性。XML是从标准通用标记语言(SGML)中简化修改出来的。它主要用到的有可扩展标记语言、可扩展样式语言(XSL)、XBRL和XPath等。

示例-0

1
2
3
4
5
6
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

HTML

超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言。HTML是一种基础技术,常与CSS、JavaScript一起被众多网站用于设计令人赏心悦目的网页、网页应用程序以及移动应用程序的用户界面。网页浏览器可以读取HTML文件,并将其渲染成可视化网页。HTML描述了一个网站的结构语义随着线索的呈现,使之成为一种标记语言而非编程语言。

HTML元素是构建网站的基石。HTML允许嵌入图像与对象,并且可以用于创建交互式表单,它被用来结构化信息——例如标题、段落和列表等等,也可用来在一定程度上描述文档的外观和语义。HTML的语言形式为尖括号包围的HTML元素(如<html>),浏览器使用HTML标签和脚本来诠释网页内容,但不会将它们显示在页面上。

HTML可以嵌入如JavaScript的脚本语言,它们会影响HTML网页的行为。网页浏览器也可以引用层叠样式表(CSS)来定义文本和其它元素的外观与布局。维护HTML和CSS标准的组织万维网联盟(W3C)鼓励人们使用CSS替代一些用于表现的HTML元素。

Read more »

既然sakila的数据不是很好用,干脆自己爬点数据吧。

  • 实战-sakila数据库项目 介绍了sakila数据开发应用,结果发现只有文字数据,而且影片数据也不理想,所以这里需要使用爬虫从万能的互联网上抓点数据。

目标选择

  • 腾讯视频

抓取类型

  • 电影

技术选型

  • jsoup

代码实现

简单的单元测试入门。jsoup的用法可以直接查看官方文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static final String UA_PHONE = "Mozilla/5.0 (Linux; Android 4.3; Nexus 10 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.23 Safari/537.36";

private static final String UA_PC = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36";

private static final String QQ_PAGE_MOVIE = "http://v.qq.com/x/list/movie?itype=-1&offset=1";

@Test
public void test() throws IOException {
Document document = Jsoup.connect(QQ_PAGE_MOVIE)
.userAgent(UA_PC)
.timeout(TIME_OUT)
.ignoreContentType(true).get();
Elements elements = document.select("li.list_item a.figure");
for (Element element : elements) {
String url = element.attr("href");
String title = element.select("img").attr("alt");
String image = element.select("img").attr("r-lazyload");
System.out.println("href " + url + " title " + title + " image " + image);
}
}
Read more »