MySQL EXPLAIN 解读

EXPLAIN 解释了 MySQL 是如何执行 SQL 语句的。使用的方法很简单,在 SQL 语句前加上 EXPLAIN 关键字就可以。
下面是一个简单的例子,测试数据在文章末尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 例 1
mysql> EXPLAIN SELECT name FROM users WHERE id = 1\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: users
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: const
rows: 1
Extra: NULL
1 row in set (0.00 sec)

EXPLAIN 列的解释:

  • id:SELECT 标识符,下面具体分析
  • select_type: SELECT 类型,下面会具体分析
  • table: 查询所使用的表
  • type: JOIN 的类型,下面会具体分析
  • possible_keys: 可能使用的索引,但不一定会真正使用
  • key: 真正使用的索引
  • key_len: 所使用的索引长度
  • ref: 与索引比较的列
  • rows: 预估需要扫描的行数
  • Extra: 额外信息

Rails Concern 源码研究

ActiveSupport::Concern 是为了更方便地 include 模块而推出的工具类。

使用方法

首先来看下它的使用方法。

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
# 传统的 Module 引入
module M
def self.included(base)
base.extend ClassMethods
base.class_eval do
scope :disabled, -> { where(disabled: true) }
end
end

module ClassMethods
...
end
end

# 使用 Concern
module M
extend ActiveSupport::Concern

included do
scope :disabled, -> { where(disabled: true) }
end

class_methods do
...
end
end

可见,通过 included 和 class_method 两个类方法使得 Module 的写法更加清晰。
你可能有些疑问,对比传统的引入,使用 Concern 虽然更加清晰了,但没什么巨大的优点。而传统的引入也可以通过将方法分成两个 module,如 InstanceMethods、ClassMethods,来达到同样的效果。
在这个简单的例子上,确实如此。但在一些嵌套的 include 上 Concern 的优势就体现出来了。

Ruby 常量查找

对 Ruby 中常量查找只有基础的认识,使用上还是有不少疑问,比如 “::A” 的含义、为什么有时使用 “A::B” 而有时直接用 “B”。
花时间查了资料来加深对此的理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module Record
def self.method
puts 'outer'
end

module Music

Record.method # outer

module Record
def self.method
puts 'inner'
end
end

Record.method # inner
::Record.method # outer
Music::Record.method # inner
end
end

我们从简单的例子出发。在上文的代码中,共存在着两个名为 Record 的 module。一个是在最外层,一个是在 Music 内部。
当第 9 行调用 Record.method 时,由于 Music 内的 Record 还未被定义,因此调用的只能是最外层的 Record module。
而在第 17 行,当我们重新调用时,却发现调用的已经是内部的 Record 了。
可见常量的调用时是有一个先后顺序的,简单的说就是从近及远,先使用近的。虽然我们从直观上很容易理解近的含义,但严格上的顺序是通过 Module.nesting 得到的。在 Music 内部调用得到 [Record::Music, Record],可见 Record::Music 确实比 Record 有更高的优先级。

mac 上 MySQL system error 32 解决方案

不知为何,在升级了 macOS 系统之后,Rails 连接 MySQL 经常性会报 Mysql2::Error: Lost connection to MySQL server at 'sending authentication information', system error: 32 的错误。重启 MySQL 就好了,过段时间又会报错,烦不胜烦。下定决心解决下。

参考了这个帖子后,大致看到有两种解决方法。一种是调大系统 ulimit -n 的值,另一种是调小 table_open_cache 的值。
从帖子的回复以及 ulimit -n 表示系统的文件句柄限制来看,个人认为调节 ulimit -n 的值是一个治标不治本的方法。本机的值已经 4k+,应该足够了。

正巧有次出现问题时,MySQL 还连接着。查了下当时的 status,发现 open_tables 不过几十,而 open_files 却几千。
执行 flush tables 清除缓存后就可以重新连接了。确实和 table_open_cache 有一定的关系。

查看了下 table_open_cache 的值,默认值是 2000。通过在 my.cnf 中添加 table_open_cache = 500,MySQL 这几天就没有闹别扭,希望能一直安稳下去。

MySQL 相关命令

1
2
show status like '%open_files%'; # 查看 open_files 的值,open_tables 同理
show variables like '%table_open_cache%'; # 查看 table_open_cache 的值

参考

ActiveRecord select vs pluck

selectpluck 都可以从数据库读取指定的字段,但两者存在不小的差别。

1
2
3
4
5
6
7
Product.select(:id).to_a
# Product Load (0.5ms) SELECT `products`.`id` FROM `products`
# [#<Product id: 2>, #<Product id: 1>]

Product.pluck(:id)
# Product Load (0.4ms) SELECT `products`.`id` FROM `products`
# [2, 1]

select 返回的是仅含有 id 的 Product Model 数组,而 pluck 返回的是 id 的数组。
两者相比较,pluck 省却了构造 ActiveRecord 的过程,效率更优。我们可以通过 Benchmark.measure 来验证下。

1
2
puts Benchmark.measure {Product.select(:id).to_a}
puts Benchmark.measure {Product.pluck(:id)}

- user CPU time system CPU time sum CPU time elapsed real time
select 0.050000 0.020000 0.070000 0.095440
pluck 0.000000 0.000000 0.000000 0.001845

除此之外,两者还有一个区别,即查询时机的不同。

1
2
3
4
5
6
ProductOrder.where.not(id: SubOrder.where(sub_order_no: '001').pluck(:order_id))
# SELECT `orders`.* FROM `orders` WHERE `orders`.`type` IN ('ProductOrder') AND (`orders`.`id` NOT IN (SELECT `sub_orders`.`order_id` FROM `sub_orders` WHERE `sub_orders`.`sub_order_no` = '001'))

ProductOrder.where.not(id: SubOrder.where(sub_order_no: '001').pluck(:order_id))
# SELECT `sub_orders`.`order_id` FROM `sub_orders` WHERE `sub_orders`.`sub_order_no` = '001'
# SELECT `orders`.* FROM `orders` WHERE `orders`.`type` IN ('ProductOrder') AND (`orders`.`id` != 3)

在上面这个例子中,通过 pluck 的调用进行了两次查询,而 select 只进行了一次查询。可见调用 pluck 会立即进行数据库查询。

参考

Rails ETag 应用

使用 ETag 验证缓存

etag.png-27.9kB
来源:Browser Cache: How ETags Works in Rails 3 and Rails 4
可以把 ETag 看成是响应的 token,客户端在下一个同样的请求中将其发送给服务器。服务器通过 token 来判断资源是否进行修改。若未修改,则返回 304 Not Modified,使客户端不需再去下载与缓存中已有的完全相同的字节。

fresh_when

在 Rails 中,我们可以通过 stalefresh_when 方法来显式地使用 ETag。

1
2
3
4
5
6
7
8
9
10
class ProductsController < ApplicationController

# This will automatically send back a :not_modified if the request is fresh,
# and will render the default template (product.*) if it's stale.

def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, etag: @product
end
end

上例中通过 `@product.published_at.utc` 来判断资源是否过期。

默认开启的 ETag

最近才发现不用 fresh_when 返回的请求也会自带 ETag,查了下原来 Rails 框架默认使用 Rack::ETag middleware,会自动给无 ETag 的 response 根据其 body 添加上 ETag。

DOM 事件流

事件流

“DOM2级事件”规定的事件流包括三个阶段:

  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段

事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。
事件冒泡指事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点。

event_flow.png-69.4kB
来源:W3C
以上图的 HTML 页面为例,单击 <td> 会按照上图所示顺序触发事件。

事件委托

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

1
2
3
4
5
<ul>
<li>TODO 1</li>
<li>TODO 2</li>
<li>TODO 3</li>
</ui>

以上面的 HTML 代码为例,按照传统的做法,若要使每个列表项单击后执行相关操作,则需要分别为它们各自添加 click 事件,共 3 个事件。若后续增加了更多的列表项,还需额外为它们添加相应的事件。
而使用事件委托,则只需在 <ul> 上添加 click 事件即可。由于所有列表项都是这个元素的子节点,而且它们的事件会冒泡,所以单击事件最终会被 <ul> 的事件处理。与前面的传统做法相比,事件委托占用的内存更小且更为便捷。

参考

  • JavaScript 高级程序设计 第 13 章

FactoryGirl 技巧

简洁地设置 Association

1
2
3
factory :post do
association :author, factory: :author
end

我们可以通过以上来设置相关的 association,但如果 association 的名字与它 factory 的名称是相同的,我们就可以省略其 factory 的设置。

1
2
3
factory :post do
auther
end

继承

1
2
3
4
5
6
7
8
9
10
11
factory :post do
title "A title"

factory :approved_post do
approved true
end
end

approved_post = create(:approved_post)
approved_post.title # => "A title"
approved_post.approved # => true

也能明确指定 parent。

1
2
3
4
5
6
7
factory :post do
title "A title"
end

factory :approved_post, parent: :post do
approved true
end

Javascript == 和 === 区别

比较

最主要的区别,== 会在比较前进行转化,而 === 比较前不进行转化。

1
2
var result1 = ('55' == 55) // true,因为转换后相等
var result2 = ('55' === 55) // false,因为不同的数据类型不相等

相等 ==

相等操作符会先转换操作数(通常称为强制转型),然后再比较它们的相等性。

在转换时:
如果有一个操作数是布尔值,则在比较相等性之前将其转换为数值—— false 为 0,true 为 1。
如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值。
如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf() 方法,用得到的基本类型值按照前面的规则进行比较。

在比较时:
null 和 undefined 是相等的。
要比较相等性之前,不能将 null 和 undefined 转换成其他任何值。
如果有一个操作数是 NaN(两个 NaN也是),则相等操作符返回 false。
如果两个操作数都是对象,则比较它们是不是同一个对象。如果指向同一个对象则返回 true。

全等 ===

除了在比较之前不转换操作数之外,全等和不全等操作符与相等和不相等操作符没有什么区别。它只在两个操作数未经转换就相等的情况下返回 true。

参考

  • JavaScript 高级程序设计 P52

Rails & MySQL 使用 emoji 时 Incorrect string value 解决方法

当在 charset 为 utf-8 的 MySQL 中插入 emoji 时,数据库会报错 Incorrect string value
仔细一想,这很奇怪。因为 emoji 本身可以通过 utf-8 来表示,作为 utf-8 的 MySQL 表来说报错实在是太不应该了。
查了之后,才发现原来 MySQL 的 utf-8 是不完全的,只支持 1-3 个字节,而 emoji 的 utf-8 编码大多为 4 个字节。为了支持更多的字符集,MySQL 5.5 推出了编码 utf8mb4,使其可以兼容 4 字节的 emoji。
因此,若要让数据库支持 emoji,可以将编码从 utf-8 改为 utf8mb4。

对于新创建的表这样没有问题,但对于旧有的表来说更改字符集代价很大。有没有一个更简单的方法来实现呢?
对 Rails 来说是有的。可以通过 serialize 来实现。

1
2
3
class Post < ActiveRecord::Base
serialize :content
end

这时存储 emoji 时,就会先进行 serialize 再存入数据库。由于不是直接以 emoji 的 utf-8 编码存储,MySQL 也就不会报错了。

参考