jonathan-fei.pro


  • Home

  • Tags

  • Categories

  • Archive

N + 1 问题的解决方案

Posted on 2018-02-07 | In Rails

Rails中的Active Record的关联功能很强大,但是有时会产生N+1 SQL 查询问题。比如查看一篇文章下的很多评论就会产生N+1 问题。
PS: 问题往往出现在一对多和多对多的关系中。
参考资料: ihower实战圣经

解决思路如下:

1. 安装检测工具

(1)gem 'rack-mini-profiler' ,效能检测工具。安装后,网页上左上角会出现网页加载时间的提示。
(2)gem 'bullet', group: 'development', N+1 query检测工具。使用流程可参考:bulldet,当你的代码存在N+1问题时,会出现例如下面的提示:

1
2
3
4
USE eager loading detected
Post => [:user]
Add to your finder: :includes => [:user]
Call stack

2. 使用 inclueds 关联

(1)model 之间的关系

1
2
3
4
5
6
7
class User < ApplicationRecord
has_many :posts
end

class Post < ApplicationRecord
belongs_to :user
end

(2)修改控制器中代码

1
2
3
4
5
class PostsController < ApplicationController
def index
posts = Post.includes(:user).page(params[:page])
end
end

此时,就会观察到rails log中只会出现两条SQL查询语句,。

1
2
Comment Load (0.5ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 1]]
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (4, 18, 2, 14, 13, 3, 12)

N + 1 其实属于是慢查询的问题,下面就大体总结下解决慢查询问题及解决方法:

  • N + 1 ——– ——–includes
  • 大量数据 ———– find_each(或者 find_in_batches)
  • 查询慢 ————– 加 索引
  • 捞出所需字段 —— select 方法
  • 计数快取 ———- Counter cache

使用 carrierwave 设置默认头像

Posted on 2018-02-06 | In Rails

最近项目中需要实现头像上传功能的API接口,并且可以设置默认头像,这篇文章来大概总结一下具体的实现过程。

Rails中 实现图片上传最常用的是使用“gem carrierwave”。
github 地址: carrierwave

Step1

安装gem "carrierwave" gem "mini_magick"

Step2

1
rails g image uploader

这个命令会生成image_uploader.rb这个文件。之后关于图片的设置都需要在这个文件里设置。下面是我设置的配置文件。

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
 class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick


storage :file

def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end

# 设置图片大小,分为三个尺寸
process resize_to_fit: [800,800]

version :thumb do
process resize_to_fill: [200,200]
end

version :medium do
process resize_to_fill: [400,400]
end

#设置图片的默认url地址,比如我要实现默认头像的设置,就会用到这个方法
def default_url(*args)
"/uploads/tbl_playerinfo/headimg/1/" + [version_name, "14563MHK40-13251.jpg"].compact.join('_')
end

# 图片格式的设置
def extension_whitelist
%w(jpg jpeg gif png)
end
end

Step3

设置model。table中的哪个字段存储图片就需要设置这个字段。

1
2
3
4
class TblPlayerinfo < ApplicationRecord
# headimg 为存储用户头像的字段
mount_upload :headimg, ImageUploader
end

Step4

显示上传图片的url地址,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if @player.present?
@gamer = @player.first
json.code 0
json.msg "已找到该玩家"
json.playerMsg do
json.uid @gamer.userid
json.name @gamer.nickname
json.gold @gamer.gold
json.online true
json.sex @gamer.sex
json.viptype @gamer.account.vip_type
json.headimgurl @gamer.headimg_url
json.score @gamer.score
json.diamond @gamer.diamond
end
else
json.code 1
json.msg "玩家不存在"
end

其中可以使用字段名加url后缀来直接显示图片的url地址。如headimg_url

总结:昨天一直想实现默认头像的功能,起初是在headimg这个字段设置默认值,但是捞出来的url总是错误的。后来仔细看了carrierwave的说明文档,才搞懂该如何设置默认的文件地址。教训:遇到问题要第一时间看官方文档和源代码,相比于在网络上寻找教程不仅可以提高自己的解决问题的效率,而且还能让自己养成看原始文档的好习惯。

补充:

1
2
3
4
class TblPlayerinfo < ApplicationRecord
# headimg 为存储用户头像的字段
mount_upload :headimg, ImageUploader
end

其中headimg字段在数据库中的数据被记录为图片的名称。如果想要直接从数据库中直接获取完整的图片url地址,可以添加一个字段headimg2 用来代替headimg,然后headimg的值可以设置为服务器地址+headimg2, 例如

1
2
3
4
5
6
7
8
9
10
11
12
13
if @player.update(:headimgxf => params[:image])
@player.update( :headimg => "47.100.60.84" + @player.headimgxf.to_s )
json.code 0
json.msg "上传成功"
json.playerMsg do
json.uid @player.userid
json.name @player.nickname
json.headimgurl @player.headimg
end
else
json.code 1
json.msg "上传失败"
end

这样从数据库中捞取headimg字段的数据时就是完整的url地址了。

Rails 中的数据查询方法

Posted on 2018-02-06 | In Rails

Rails中的Acvtice Record使得我们不用写SQL语句就可以查询数据库中的数据。
当使用Rails中的方法查询数据时,过程大体是这样的:

  • 将查询选项转为sql语句
  • 触发sql语句在数据库中查询
  • 将查询结果实例化(变成对应的模型对象)

1– 查询单个对象

(1)find方法
find方法用于查询指定主键的对象;Author.find(1)

(2) find_by方法
find_by 方法会查询到符合条件的第一条记录
Author.find_by(:name => "鲁迅") 也可以写成
Author.find_by name: "鲁迅"

(3)take方法
take方法会查询到一条记录,往往是数据表中的第一条记录。
Author.take

2– 查询多个对象

我们常常使用each方法,用来检索数据表中的记录。它的原理是会让Active Record 捞出整个数据表的数据,然后对每条记录创建模型对象,并把整个模型对象以数组形式保存在内存中。如果数据量很多的话,这样明显是行不通的。Rails有两种方法可以解决这个问题:
(1)find_each
工作原理:find_each方法每次检索一批记录,然后把每条记录实例化成模型对象传入块。默认每次检索1000条记录。

1
2
3
Player.find_each do |player|
player.name
end

a. 使用batch_size 来指定每次检索的记录总数

1
2
3
Player.find_each(batch_size: 5000) do |player|
player.name
end

b. 使用start 实现从起始点进行ID检索 ,比这个ID小的都不会取回。并且这个ID必须是主键。平常情况下较少用这个方法。

1
2
3
4
# 检索id从10开始的作者姓名
Player.find_each(start: 10) do |player|
player.name
end

c.使用finish 实现从起始点进行ID检索,比这个ID大的都不取回。与start类似。

1
2
3
4
# 检索id为10000到20000的作者姓名
Player.find_each(start: 10000, finish: 20000) do |player|
player.name
end

(2)find_in_batches
工作原理:find_in_batches 方法每次检索一批记录,然后把每批记录实例化成模型数组传入块。用法和find_each 类似。

3.条件查询

(1)数组条件中的占位符
a. ?

1
2
3
$ Player.where("age >= ?", params[:age])
或者
$ Player.where("player.age >= ?", params[:age])

b. 字符
如果条件中有很多变量,那么以下做法会更易于代码的阅读。

1
$ Player.where("age >= :age AND saler >= :wage_level_", { age: params[:age], wage_level: params[:wage_level]})

(2)使用not 反向查询
查询年龄在30岁以上的玩家

1
2
3
$ Player.where("diamond > ?", 30)
也可以写成
$ Player.where.not(" diamond <= ?", 30)

4. 查询特定字段

使用find、find_by、where方法时会返回table的全部字段,若想返回指定的字段可以使用select ,比如查询年龄大于20 的玩家名字。
(1) select方法

1
2
3
4
5
Player.select(:name).where("age >= ?", 30)
查询名字和id
Player.select(:userid, :name).where("age >= ?", 30)
&
Player.select("userid, name").where("age >= ?", 30)

(2)pluck方法
pluck 方法与select方法不同,select返回是一组对象模型,pluck方法是返回字段数组,因此在数据量很大的情况下使用pluck会好很多。

1
2
3
4
5
6
Player.select(:nickname, :userid).map{ |u| [u.userid, u.nickname]}
相当于
Player.pluck(:nickname, :userid)

PS:map 的另一种写法
Player.select(:userid).map(&:userid)

pluck方法会触发即时查询,个人理解为立即查询,所以当其他查询在pluck之前时,会执行成功;在pluck之后时,会查询失败。例如:

1
2
3
Player.pluck(:userid).limit(5) #=> undefined method `limit

Player.limit(5).pluck(:userid) #=> [1], [2], [3], [4], [5]

(3)ids方法
ids方法会获得模型的主键,rails中模型主键默认为id,返回的是数组。

1
2
3
4
5
6
7
8
9
10
11
class Player < ApplicationRecord
self.primary_key = "userid"
end

Player.ids
相当于
Player.pluck(:userid)
或
Player.select(:id).map(&:userid)
或
Player.select(:id).map{ |u| u.userid}

5.关联查询

(1) joins 方法
示例代码:

1
2
3
4
5
6
7
class Player < ApplicationRecord
has_many :groups, class_name: "GroupMsg", foreign_key: "userid"
end

class GroupMsg < ApplicationRecord
belongs_to :owner, class_name: "Player", foreign_key: "userid"
end

1
$ GroupMsg.joins(:owner)

这条语句的意思为所有属于某个用户的圈子作为一个GroupMsg对象返回。

1
2
3
$ GroupMsg.joins(:owner).where(:userid => 2)
查询结果相当于
$ Player.find_by(userid: 2).groups

1
$ Player.joins(:groups)

这条语句的意思为查询所有创建了圈子的用户作为Player对象返回(可以简单理解为查询创建过圈子的用户)。如果一个用户创建了很多圈子,那么也会被重复列出。

(2)includes方法
includes可以解决N+1queries问题,因为它可以及早加载关联。

1
Player.includes(:groups)

这条命令会取出所有的用户和所有的关联圈子。
对应的SQL语句为

1
2
3
#=> 假设只有五个用户
select * from players
select * from group_msgs where group_msgs.userid in (1,2,3,4,5)

1
GroupMsg.includes(:owner)

这个命令会捞出所有的圈子和相关联的用户。
对应的SQL语句为

1
2
select * from group_msg
select * from players where player.userid = [2,3,4]

Ruby 基础--学习笔记

Posted on 2018-02-02 | In Ruby

1。each、map与collect

(1)each方法是典型的迭代器,它的作用是可以遍历数组中的所有元素,并对其进行处理,但不改变原数组的值,返回结果也是原数组的值。

1
2
3
4
5
6
sum = 0
a = [1,2,3,4,5]
a.each do |item|
sum+=item
end
print sum #=> 15

1
2
3
a = [1,2,3,4,5]
a.each{ |item| item*2} #=> [1,2,3,4,5]
a #=> [1,2,3,4,5]

(2) 使用map与collect方法会形成一个新数组,原数组的值不变。

1
2
3
4
5
6
7
8
9
a = [1,2,3,4,5]
a.each{ |item| item*2} #=> [1,2,3,4,5]
a #=> [1,2,3,4,5]

a.map{ |item| item*2} #=>[2,4,6,8,10]
a #=> [1,2,3,4,5]

a.collect{ |item| item*2} #=> [2,4,6,8,10]
a #=> [1,2,3,4,5]

(3)使用map!与collect!会形成一个新的数组,并改变原数组的值。

1
2
3
4
5
6
a = [1,2,3,4,5]
a.map!{ |item| item*2} #=> [2,4,6,8,10]
a #=> [2,4,6,8,10]

a.collect!{ |item| item*2} #=> [2,4,6,8,10]
a #=> [2,4,6,8,10]

2。作用域

(1)作用域门

1
2
3
4
5
6
7
8
9
10
11
12
13
v1 = 1
class Myclass
v2 = 2
local_variables #=> [:v2]
def my_method
v3 = 3
local_variables
end
local_variables #=> [:v2]
end

obj = Myclass.new
obj.my_method #=> [:v3]

当进行类定义(关键字:class)、模块定义(module)、方法定义(def)会产生作用域。

(2)穿越作用域

1
2
3
4
5
6
7
8
9
10
11
var = "scope"
class Myclass
puts "#{var}"

def my_method
end
end

#=> NameError: undefined local variable or method `var' for Myclass:Class

得: 局部变量无法穿过Myclass的作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
var = "scope"
Myclass = Class.new do
puts "#{var} in the #{self.class}"
def my_method
puts "#{var}"
end
end
#=> scope in the Class
#=> undefined local variable or method `my_var' for Myclass:Class (NameError)

得:
(1)通过方法调用可以让局部变量穿过Myclass的作用域;
(2)局部变量var无法穿过my_method的作用域。
1
2
3
4
5
6
7
8
9
10
11
12
13
var = "scope"
Myclass = Class.new do
puts "#{var} in the #{self.class}"
define_method :my_method do
puts "#{var} in the #{self.class}"
end
end

obj = Myclass.new
obj.my_method

#=> scope in the Class
#=> scope in the Myclass

3. 单引号和多引号的区别

Ruby中生成字符串有两种方式,单引号和双引号,其中单引号不被转义,多引号会被转义。

1
2
$ name = "jonathan" #  => "jonathan"
$ string = "#{name}" # => "jonathan"

1
2
$ name = 'jonathan' # => "jonathan"
$ string = '#{name}' # => "\#{name}"

4. Symbol

在散列中会常常用到符号(Symbol)。例如address = { :name => "joanathan", :phone => "123456"} 其中:name :phone 就是符号。符号可以和字符串相互转换。

1
2
3
4
5
name = :jonathan #=> :jonathan
等同于 name = :"jonathan"

name1 = name.to_s #=> "jonathan"
name1.to_sym #=> :jonathan

5. Ruby中的实例变量

(1)Ruby中的对象包含实例变量,如果想要对象中的实例变量,需要调用对象的方法。示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person

def name
@name = "Tim"
end

def name=(vaule)
@name = vaule
end

def kill
@killer = name
end
end

下面就来做些小实验来验证需要调用方法才能访问对象中的实例变量。

1
2
3
tim = Person.new
puts tim.name #=> "Tim"
puts tim.instance_variables #=> [:@name] 此时没有调用kill方法,所以在tim这个对象调用instance_variables这个方法时没有@killer这个实例变量

1
2
3
4
tim = Person.new
puts time.name #=> "Tim"
puts tim.killer #=> "Tim"
puts tim.instance_variables #=> [:@name, :@killer]
1
2
Tim = Person.new
puts Time.name = "Jonathan" #=> "Jonathan"

由此得知:Ruby如果想要访问对象中的实例变量或对其赋值,那么就需要调用对象的方法。
(2)存取器
存取器的定义为attr_accessor ,Ruby元编程中称为”类宏”,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person
attr_accessor :name
end

相当于
class Person
def name
@name = "Tim"
end

def name=(vaule)
@name = vaule
end
end

1
2
3
tim = Person.new
p tim.name = "Jonathan" #=> "Jonathan"
p tim.name #=> "Jonathan"
1
2
3
tim = Person.new
p tim.name #=> nil 因为@name的值为空
p tim.name = "Joanthan" #=> "Jonathan"

6. << 符号

在数组中,经常会用到 <<,示例代码:

1
2
3
ary = [1,2,3]
ary << 4
ary = [1,2,3,4]

Ruby 中的 self

Posted on 2018-02-02 | In Ruby

self 指的是当前对象。在调用方法时,分为两个步骤,查找方法和执行方法,调用方法的对象成为接收者,也就是当前对象。没有哪个对象可以一直充当当前对象的角色,并且,任何时刻,只有一个对象充当当前对象。

一. self 的含义

1。在class关键字(类)内部,self指的是类本身。

1
2
3
4
class Myclass
puts self
end
# Myclass

2。def 定义的方法内部,self指的是调用这个方法的对象(当前对象)

1
2
3
4
5
6
7
class Myclass
def my_method
pust self
end
end
$ obj = Myclass.new #=> #<Myclass:0x007f90ee0e1830>
$ obj.method #=> #<Myclass:0x007f90ee0e1830>

3。 self写在def关键字的后面,self指的是类本身

1
2
3
4
5
6
7
8
9
10
class Myclass
def self.my_method
puts self
end
end
$ obj = Myclass.new #=> #<Myclass:0x007fe83294de88>

$ obj.my_method #=> NoMethodError: undefined method `my_method' for #<Myclass:0x007fe83294de88>

$ Myclass.my_method #=> Myclass

由此可见,当self出现在def关键字后面时,self指的是Myclass这个类,而非Myclass类的实例obj。

二. 显式self与隐式self

《ruby元编程》中提到私有规则:私有方法只能通过阴性的接收者调用。另:当一个对象被创建时,那么它就获得了它所属类中的实例方法。
1。 ruby中定义的方法不特殊说明时,默认为public方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A
public #可省略
def method1
puts "I am method1"
self.method2
end

def method2
puts "I am method2"
end

def method3
puts "I am method3"
method2
end
end

$ obj = A.new
$ obj.method1
#=> I am method1
#=> I am method2
$ obj.method3
#=> I am method3
#=> I am method2

2。 private 定义私有方法

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
class A
public #可省略
def method1
puts "I am method1"
self.method3
end

def method2
puts "I am method2"
method3
end

private

def method3
puts "I am method3"
end
end

$ obj = A.new
$ obj.method3 #=> private method `method3' called for #<A:0x007fc1c9111f60> (NoMethodError)

$ obj.method1 #=> private method `method3' called for #<A:0x007f8f9e822230> (NoMethodError)

$ obj.method2
#=> I am method2
#=> I am method3

由此得知,调用私有方法时不能明确指定接收者是谁,也就是只有隐性的接受者才能调用私有方法。

三. 方法访问级别

1。private 与 protected

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Father

def method_a
puts "I am METHOD A in #{self.class}"
end

def method_b
puts "I am METHOD B in #{self.class}"
self.method_c #self 可以省略
end

protected
def method_c
puts "I am METHOD C in #{self.class}"
end

private
def method_secret
puts "I am method_secret in #{self.class}"
end

end


class Son < Father

def son_method_c
method_c
end

def son_method_d
self.method_c
end

def son_method_secret
self.method_secret
end

end

$ son = Son.new
$ son.son_method_c #=> I am METHOD C in Son
$ son.son_method_d #=> I am METHOD C in Son
$ son.son_method_b
#=> I am METHOD B in Father
#=> I am METHOD C in Father
$ son.method_c #=> NoMethodError: protected method `method_c' called for
$ son.method_secret #=> NoMethodError: private method `method_secret' called for
$ son.son_method_secret #=> NoMethodError: private method `method_secret' called for


$ father = Father.new
$ father.method_c #=> NoMethodError: protected method `method_c' called for

由此得知:
(1)在调用protected和private级别的方法时,不管是同类的对象(father)还是子类的对象(son),都不能够对其直接调用;只能通过其他方式,比如在别的方法中引用。
(2)ruby中的私有方法只能被阴性self调用;public方法和protected方法可被显示self和隐式self调用。

Rails生成乱数ID的实现方法

Posted on 2018-01-08 | In Rails

在ROR网站开发中,当我们查询某笔资料时,一般情况下,网址是这样的,如下图:
enter image description here

这样做的缺陷很明显,就是用户可以通过网址猜到我们数据库中user的数据量。为了解决这个问题,我们可以通过把ID改成乱数ID的形式解决,就像下面这样:
enter image description here

实现过程如下:

step1:model层的实现

1- 为 user添加friendly_id 字段

1
$ rails g migration add_friendly_id_to_users

2- 在新生成的migration文件中,为friendly_id 添加唯一索引

1
2
add_column :users, :friendly_id, :string
add_index :users, :friendly_id, :unique => true

1
$ rake db:migrate

3- 修改user.model

1
2
3
4
5
6
7
8
9
before_validation :generate_friendly_id, on: :create

def to_param
self.friendly_id
end

def generate_friendly_id
self.friendly_id ||= SecureRandom.uuid
end

其中to_param Rails中自带的方法,我们在这里对它进行重新定义。之前的定义为,

1
2
3
def to_param
self.id
end

它的作用转换ID,例如user_path(current_user) 就相当于 user_path(current_user.to_param) ,也相当于 user_path(current_user.id)。

Step 2 controller层

通常我们的users_controller.rb 会是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Class UsersController < ApplicationController
# 事例代码
before_action :find_user, only: [:show, :edit, :update]

def show
end

def edit
end

def update
if @user.update
flash[:notice] = "更新成功"
redirect_to users_path
else
render 'edit'
end

private

def find_user
@user = User.find(params[:id])
end
end

我们对其中的 find_user这个方法 做如下修改

1
2
3
def
@user = User.find_by_friendly_id!(params[:id])
end

到这里就可以实现乱数ID的显示了。

豆知识补充

1- “||= ” 符号

a ||= b 相当于 a = a || b ,它的意思为当a的值为nil时,将b的值付给a。例如 上面提到过的self.friendly_id ||= SecureRandom.uuid, 当self.friendly_id的值为空时,将SecureRandom.uuid赋值给它。

数据库的工作原理(一)

Posted on 2018-01-08 | In 数据库

开始之前的说明:最近在学习数据库的相关知识,找到了一篇很好的学习文章,写的蛮深入,同样内容也很长,所以理解起来有些难度。为了让自己更好的理解数据库的工作原理,我把自己对这篇文章的理解,写成了这篇博客(共计三篇),旨在提高自己的学习效果。
原文地址:http://blog.jobbole.com/100349/。

前言:在了解数据库是如何工作的内容之前,需要知道一些基本概念。在很久以前,开发人员需要把常用的算法和数据结构牢记于心,因为当时计算机的处理能力较差,无法承受对cpu的浪费。研究数据结构和算法的目的,或者说选择哪个数据结构和算法,是为了以更快的速度得到数据处理的结果。判断一个算法优劣的因素大体上有三个:
(1)时间复杂度
(2)算法的内存消耗
(3)算法磁盘I/O消耗
这里主要介绍时间复杂度的概念。
PS:《数据库工作原理(一)》主要梳理相关的基础概念。

一、 时间复杂度

时间复杂度指的是某个算法处理一定量数据所要消耗的时间。这个时间我们无法进行准确的计算,但是可以通过“某个算法处理一定量的数据需要多少次运算”来间接的判断算法的优劣。这里我们使用O(某函数)这样的方式来表示某个算法处理一定量的数据进行的运算次数,所需的运算次数即函数计算出的值,比如像下面这样。
假设有1000000笔数据,各个算法的运算次数如下:

  • O(1) 算法会消耗 1 次运算
  • O(log(n)) 算法会消耗 14 次运算
  • O(n) 算法会消耗 1,000,000 次运算
  • O(n*log(n)) 算法会消耗 14,000,000 次运算
  • O(n^2) 算法会消耗 1,000,000,000,000 次运算

这样我们就很容易得知,O(1)算法是我们首要选择的,O(n^2)是我们绝不会选择的算法。

了解了时间复杂度的概念以及如何表示时间复杂度后,接下来的内容会容易理解很多,比如:

  • 一个好的哈希表会得到O(1)复杂度,这是最理想的状态
  • 一个均衡的树会得到O(log(n))复杂度,其中n代表数据量,即要处理数据的数量大小
  • 一个阵列会得到O(n)复杂度
  • 最好的排序算法具有O(n*log(n))复杂度
  • 糟糕的排序算法具有O(n^2)复杂度。

这里的哈希表、B树、阵列,它们是不同的数据结构。根据他们能够得到的时间复杂的不同,我们当然会尽可能选择时间复杂度低的那一个。

二、数据结构

数据结构可以类比为房屋结构,就像不同屋子有不同的结构一样,每种不同的房屋结构会有不同的用途。比如工业厂房会很大,而且比较空旷,用于进行工业生产;写字楼的结构比较复杂,用于人们上班办公;居民房的结构相比会小一些,用于居住等。数据结构也一样,不同的数据结构有不同的适用场景,我们不能直接评论数据结构的好坏之分,但是会有一个评判标准,就是处理数据的速度。

如上所说的一样,不同的房屋结构适用于不同场景。数据结构也一样,有的数据结构善于处理这种情况,有的数据结构善于处理那种情况。例如:

  • 阵列,用于数据量比较小的情况,它的时间复杂度为 O(n)
  • 树,善于处理“查找一个确定值”的情况,它的时间复杂度为O(log(n)),树是阵列的优化。
  • B树,善于处理“查找范围值”的情况,它需要消耗 M+log(N)次运算
  • 哈希表,用于快速查找某个值。

数据结构是数据库内部的基本组件。

三、数据库概览

数据库说白了,其实是一堆文件的集合,一个信息集合,对这一堆文件进行精心的设计就是数据库干的工作。比如可以快速处理数据(如查询),使用事务来保证数据的安全和唯一性等。

数据库是信息的集合,那么可以把它想象成数据的仓库。既然是仓库,那么就需要管理,管理就需要工具,数据库的各种组件就属于是工具,他们用来管理这个大仓库中的不同事务。

这张图上列出了数据库内的各组件。包括客户端管理器、查询管理器、数据管理器等。

下一篇将会着重介绍数据库的一个很重要的管理器–查询管理器。

Rails项目部署

Posted on 2018-01-06 | In Ruby

基本配置:
(1)服务器类型:阿里云服务器
(2)镜像系统:Ubuntu 16.04 64位
(3)web服务器: Nginx + Passenger
(4)数据库服务器:MySQl

基本知识:

  • 基本CLI指令。
    date 会显示系统时间
    uname -a 显示系统版本
    uptime 查电脑开机多久了
    which 查询执行档的确切位置
    history 查询刚刚执行过的指令

  • vi 、nano、vim是三种文档编辑器,nano与vim是操作系统内建的。

  • 传档案:SSH 通信协议除了可以让我们登入远端的服务器,也可以作为档案传输使用,又叫做 SFTP。例如:

    1
    scp test.txt root@106.14.190.181:~/

    这个命令的意思是将本机的test.txt文件传到服务器上root账号的 ~/ 文件夹下;

    1
    scp root@106.14.190.181:~/test.txt ./

    这个命令的意思是将服务器上的text.txt 文件传到本机

  • 压缩与打包:如果要向服务器传很多档案,那么使用压缩打包的方式会很快;压缩和打包是可以分开的:gzip 只能压缩一个档案、tar 可以打包整个目录顺便压缩。

    • gzip 压缩档案
    • gzip -d 解压缩档案
    • tar zcvf xxx.tar.gz xxx将 xxx目录打包并压缩成 xxx.tar.gz 档案
    • tar zxcf xxx.tar.gz 将 xxx.tar.gz 解压缩

一、更新并安装Linux套件

apt-get 是Ubuntu内建的套件管理工具。拿到一台服务器,首选应该做的是更新上面的套件。
1- 执行 pt-get update 。

2- 执行 apt-get upgrade -f 。

3- 设定时区:dpkg-reconfigure tzdata,进入菜单后选择Shanghai。

4- 安装Ruby on Rails所需 的东西:

1
apt-get install -y build-essential git-core bison openssl libreadline6-dev curl zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 autoconf libc6-dev libpcre3-dev libcurl4-nss-dev libxml2-dev libxslt-dev imagemagick nodejs libffi-dev

二、安装Ruby

使用Brighbox编译好的Ruby套件进行安装。
1- 执行 apt-get install software-properties-common

2- 执行 apt-add-repository ppa:brightbox/ruby-ng

3- 执行 apt-get update

4- 执行 apt-get install ruby2.4 ruby2.4-dev

执行完上面四个命令后,执行 ruby -v检查ruby是否成功安装
5- 安装Bundler,执行gem install bundler --no-ri --no-rdoc --source https://gems.ruby-china.org

三、安装MySQL

1- 执行 sudo apt-get install mysql-common mysql-client libmysqlclient-dev mysql-server,然后根据提示设置登录密码

2- 执行mysql -u root -p 登录数据库,输入CREATE DATABSE your-database-name CHARACTER SET utf8mb4

四、安装Nginx + Passenger

Nginx是目前很流行的一种网站服务器,我们用它来处理静态资源,例如CSS/JS;Passenger 是一个 app server,支持基于 Rack 框架的 Ruby app(包括 Rails)。Passenger 的特点是需要作为模块编译到 Nginx 中,优点是配置简单,不需要自己写启动脚本。以下是安装步骤:

1- 通过apt安装Passenger,这里需要导入Passenger密钥

1
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7

2- 安装apt插件以支持http传输

1
apt-get install -y apt-transport-https ca-certificates

3- 添加apt的源

1
sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list’

之后执行 apt-get update

4- 安装Passenger的包

1
apt-get install -y nginx-extras passenger

修改Nginx配置,编辑/etc/nginx/nginx.conf
改为下面这样:

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
34
35
36
  # 让 Nginx 可以读到环境变量 PATH,Rails 需要这一行才能调用到 nodejs 来编译静态档案。
+ env PATH;

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
worker_connections 768;
# multi_accept on;
}

http {

# 关闭 Passenger 和 Nginx 在 HTTP Response Header 的版本资讯,减少资讯洩漏
+ passenger_show_version_in_header off;
+ server_tokens off;

# 设定档案上传可以到100mb,默认只有1Mb超小气的,上传一张图片就爆了
+ client_max_body_size 100m;

gzip on;
gzip_disable "msie6";

# 最佳化 gzip 压缩
+ gzip_comp_level 5;
+ gzip_min_length 256;
+ gzip_proxied any;
+ gzip_vary on;
+ gzip_types application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/xml text/plain text/javascript text/x-component;

# 打开 passenger 模组
- # include /etc/nginx/passenger.conf;
+ include /etc/nginx/passenger.conf;

# 下略

新增/etc/nginx/sites-enabled/example.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 80;
server_name 47.92.82.116; # 你的服务器 IP 位置

root /home/deploy/example/current/public; # example改为你自己的项目名称

passenger_enabled on;

passenger_min_instances 1;

location ~ ^/assets/ {
expires 1y;
add_header Cache-Control public;
add_header ETag "";
break;
}
}

完成Nginx的设定之后,执行 service nginx restart。 此时使用你的Ip地址打开网页如下图:

五、 安装Capistrano自动化部署

1- 本机安装gem 'capistrano-rails'与 gem 'capistrano-passenger '

2- capistrano 文件设定

执行 cap install ,新增capistrano 的配置档案,编辑以下文件:
(1)Capfile

1
2
3
4
require "capistrano/scm/git”
install_plugin Capistrano::SCM::Git
+ require 'capistrano/rails’
+ require 'capistrano/passenger’

(2)config/deploy.rb

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
34
35
36
37
38
39
40
41
42
43
44
+  sh "ssh-add"

# config valid only for current version of Capistrano
lock "3.8.1"

- set :application, "my_app_name"
+ set :application, "rails_recipes" # 请用你自己的项目名称

- set :repo_url, "git@example.com:me/my_repo.git"
+ set :repo_url, "git@github.com:growthschool/rails-recipes.git" # 请用你自己项目的git位置

# Default branch is :master
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

# Default deploy_to directory is /var/www/my_app_name
# set :deploy_to, "/var/www/my_app_name"
+ set :deploy_to, "/home/deploy/rails-recipes" # 这样服务器上代码的目录位置,放在 deploy 帐号下。请用你自己的项目名称。

# Default value for :format is :airbrussh.
# set :format, :airbrussh

# You can configure the Airbrussh format using :format_options.
# These are the defaults.
# set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto

# Default value for :pty is false
# set :pty, true

# Default value for :linked_files is []
- # append :linked_files, "config/database.yml", "config/secrets.yml"
+ append :linked_files, "config/database.yml", "config/secrets.yml"

# Default value for linked_dirs is []
- # append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
+ append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

+ set :passenger_restart_with_touch, true

# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }

# Default value for keep_releases is 5
- # set :keep_releases, 5
+ set :keep_releases, 5

(3) config/deploy/production.rb

1
2
3
+   set :branch, "master"
- # server "example.com", user: "deploy", roles: %w{app db web}, my_property: :my_value
+ server "47.92.82.116", user: "deploy", roles: %w{app db web}, my_property: :my_value, 其中user为你自己设定的用户名称

之后的步骤是根据cap production deploy:check检查缺少哪些文件,按照提示新增缺少的文件即可,一般需新增database.yml与secrets.yml这两个个文件。

最后在本机执行 cap production deploy ,完成部署。

SQL基础

Posted on 2018-01-04 | In 数据库
  • SQL,Structure Query Language,结构化查询语言,是关系型数据库的应用语言。

一、分类

  1. DDL(Data Defintion Language )语句:数据定义语言,定义了不同的数据段、数据库、表、列、索引等数据库对象,常用的语句的关键字包括 create、drop、alter等。
  2. DML(Data Manipulation Language)语句:数据操纵语句,用于增删改查数据库记录,并检查数据的完整性。关键词: insert、delete、update、select。
  3. DCL(Data Control Language)语句:数据控制语句,用于控制不同数据段直接的许可和访问级别的许可。这些语句定义了数据库、表、字段、用户的访问权限和安全级别。关键词:grant、revoke。

二、DDL语句

DDL语句一般情况下是由数据库管理员(DBA)使用,与DML的最大区别是DML只对表内数据进行操作,而不设计表的定义、结构和修改。以下命令均为在指定数据库内操作,因此不用指明是在哪里数据库里新增等,若没有指定,则需要加上数据库的名称。

  1. 创建数据库 - CREATE DATABASE dbname; (CREATE DATABASE 也可小写 )
  2. 删除数据库 - DROP DATABASE dbname;
  3. 创建表 -

    1
    2
    3
    4
    CREATE TABLE tablename( column_name_1 column_type_1_constraints,
    column_name_2 column_type_2_constraints,
    ...
    column_name_n column_type_n_constraints)
    • 查看表结构 —— DESC tablename
    • 更全面地查看表定义 —— show create table tablename \G; 这条命令可以看到创建表的SQL语句,其中“\G
      ”的意思是使得记录能够按照字段竖向排列,也就是改变我们看到的格式。
  4. 删除表 - DROP TABLE tablename
  5. 修改表-
  • 修改表类型(字段的类型)
    1
    ALTER TABLE tablename MODIFY[COLUMN] column_definition [FIRST|AFTER col_name]

例如,在mysql环境下执行,alter table emp modify ename varchar(20)。

  • 增加表字段

    1
    ALTER TABLE table_name ADD[COLUMN] column_definition [FIRST|AFTER col_name]

    例如, alter table emp add column age int(3);

  • 删除表字段 -
    ALTER TABLE table_name DROP [COLUMN] col_name;例如, alter table emp drop column age;
  • 字段改名 -
    1
    ALTER TABLE table_name CHANGE[COLUMN] old_col_name column_definition[FIRST|AFTER column_name]

例如,alter table emp change age age1 int(4);
PS: chang与modify都可以修改列,但是change即可以修改列的名称,也可以修改列的类型,而modify只能修改列的类型。

  • 修改字段的顺序 -
    例如,alter table emp add age int(2) after name; alter table emp change age age1 int(4) first;
  • 修改表名 -
    ALTER TABLE table_name RENAME [TO] new_tablename,例如, alter table emp rename emp1;

三、DML 语句

DML语句是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(update)、删除(delete)和查询(select),是开发人员日常使用最频繁的操作。

1- 插入记录

插入单条记录:
INSERT INTO table_name(field1, field2, … fieldn) VALUES(value1, value2, …valuen);例如,insert into emp(ename, hire,sal,deptno) values(‘zzx1’, ‘2000-1-1’, ‘2000’,1); 也可以不用指定字段名称,但是需要values后面的循序与字段的顺序一致。

插入多条记录:
INSERT INTO tablename (field1, field2, … fieldn)
VALUES
(record1_value1, record2_value2, … record1_valuen),
(record2_value1, record2_value2, … record2_valuen),
…
(recordn_value1, recordn_value2, … recordn_valuen);
例如insert into dept VALUES(5, “35”),(3,”232”);

2- 更新记录

更新记录:
UPDATE tablename SET filed1= value1,field2= value2, … filedn=valuen [WHERE CONDITION],例如,update emp set sal=4000 where ename=‘lisa’;`

更新多个表中的记录:
UPDATE t1,t2,…tn set t1.field=value1,tn.field=valuen [WHERE CONDITION]

3- 删除记录

DELETE FROM tablename [WHERE CONDITION]
例如,delete from emp where ename=‘lisa’;

删除多个表中的记录:
DELETE t1,t2,..tn FROM t1,t2,…tn [WHERE CONDITION]
PS: 不论是单表还是多表,不加where条件都会把表的所有记录删除;

4- 查询记录

SELECT FROM tablename [WHERE CONDITION]
例如,select
from emp;,为查询emp这个表中的所有字段。如果想要部分字段的话,需要分别写出各个字段的名字,例如 select ename from emp;

(1)查询重复记录
用到“distinct”,例如 select distinct slaer from emp;

(2)条件查询
关键词:where,例如 select * from emp where name=Lisa;
其中“=”也可以是> < >= <= != 多条件还可以使用or和and等逻辑运算符进行多条件联合查询。

(3)排列顺序和限制
语法如下:SELECT * FROM tablename [WHERE CONDTION] [ORDER BY field1 [DESC/ASC],field2 [DESC/ASC],… fieldn [DESC/ASC]]。 不写关键字默认我ASC(升序排序)。ORDER BY 后面可以跟不同的字段,每个字段可以有不同的排序。

对于排序后的记录,如果只希望显示一部分,这时就需要用到LIMIT关键字。语法如下:SELECT … [LIMIT offset_start,row_count]。 例如select * from emp order by sal limit 3; select * from emp order by sal limit 1,3;

limit 经常和 order by 一起配合使用来进行记录的分页显示。

(4)聚合
用于汇总操作,例如统计。语法如下:

1
2
3
4
5
6
SELECT [field1,field2,…fieldn] fun_name
FROM tablename
[WHERE where_condition]
[GROUP BY field1,field2,…fieldn
[WITH ROLLUP]]
[HAVING where_condtion]

其中:

  • fun_name 表示要做的聚合操作,也就是聚合函数,常用的有sum count max min
  • GROUP BY 表示要进行分类聚合的字段,比如按照部分分类统计员工数量,部门就应该写在group by 后面
  • WITH ROLLUP是可选语法,表明是否对分类聚合后的结果进行再汇总
  • HAVING 表示对分类后的结果再进行条件的过滤。PS:having和where都是过滤,where是在聚合前过滤,having是对聚合后的结果过滤。

统计总人数: select count(1) from emp;
统计各个部门的人数: select deptno,count(1) from emp group by deptno;
既统计部门人数又统计总人数:select deptno.count(1) from emp group by deptno with rollup;
统计部门人数大于1的部门: select deptno.count(1) from emp group by deptno having count(1)>1;
统计公司所有员工的公司薪水总额、最高和最低薪水:select sum(sal),max(sal),min(sal) from emp;

(5)表连接
表连接分为内连接和外链接,内连接仅仅选出两张表中互相匹配的记录,而外连接会选出其他不匹配的记录,最常用的是内连接。
例如,查询所有雇员的名字和所在的部门名称,因为雇员名称和部门分别放在表emp、dept中,因此需要使用表连接来查询。select ename,deptno from emp,dept where emp.deptno=dept.deptno;

(6)子查询
某些情况下,当进行查询的时候,需要的条件是另外一个select语句的结果,这时就会用到子查询。关键字:in、not in、=、exists、not exists等,例如, select * from emp where deptno in(select deptno from dept)。
PS:表连接在很多情况下用于优化子连接。

四、DCL语句

DCL语句主要是DBA用来管理系统中的对象权限时使用,开发人员很少使用,例如

1
mysql >  grant all PRIVILEGES on db_name.* to 'username'@'xxx.xxx.xx.x' identified by 'password' WITH GRANT OPTION;

grant 为放出权限,select为收回权限。

数据库的事务与锁

Posted on 2018-01-03 | In 数据库

学习数据库的事务与锁之前需要先了解下什么是并发:

  • 多个用户对同一数据进行交互叫做并发。如果不加以控制,并发可能引起很多问题。数据库提供了可以合理解决并发问题的方案。

一、事务

  • 事务的概念:类比于银行转账,A转给B100元,那么A的账户减少500,B的账户增加500元,这个两个操作必须全部执行,要么就全部不执行。数据库的的事务总是将指定的一条语句或多条语句看成一个全部执行或者全部不执行的最小组合全部组合。
  • 事务类型:如SELECT INSERT UPDATE DELETE

1- 显示事务处理模式

通过START TRANSACTION 标记事务起始点,如果开启事务后执行的语句是按照期望正确执行的则以COMMIT TRANSACTION 来结束事务;如果开始事务后执行的语句结果不是所期望的,并希望取消刚才的操作,则以ROLLBACK TRANSATION 来结束事务,(相当于撤销),该动作成为回滚。前滚指的是再次执行一次事务中的操作。

2- 自动提交事务模式

默认情况下,MySQL是自动提交的。在该模式下,每条语句都被认为是一个事务。当每个SQL语句执行完成后,不是被提交就是被回滚,如果执行成功则提交,执行失败则回滚。
注意:在该模式下,执行的语句如果出现编译错误(例如关键字错误)而非运行错误该批语句都不会执行,而不是执行后再回滚;如果是运行错误不会导致该批语句都不执行,而是错误语句会回滚,之前语句正常执行。

3-隐式事务处理模式

可以通过 SET IMPLICIT_TRANSACTIONS ON 和 SET IMPLICIT_TRANSACTIONS OFF 来启动和关闭隐式模式。与显示事务处理模式相比,它省略了事务起始点,也就是START TRANSATION。

PS:事务也是可以嵌套的,这里不进行详细说明。

二、并发访问引起的问题

并发:两个用户或者两个以上的用户在同一时间与同一对象进行交互。例如春节抢票,抢的人越多,并发数就越高,对系统性能要求就越高。

1- 丢失更新

当两个或多个事务对同一数据最初的值进行更新时,由于每个事务都不知道其他事务的存在,最后提交的事务中的更新操作就会覆盖掉其他事务所做的更新,这就导致其他事务的更新操作丢失。

2- 脏读

事务完成数据更新后,这时其他事务去查询该行数据的时候读取的数据是临时的,如果最后更新的事务被回滚,这个临时数据对于查询的事务来说就是“脏数据”。

3- 不可重复读

在一个事务两次的查询之间,同一数据被其他事务更新,导致同一事务中的两次查询结果不同,这样的现象叫做不可重复读。确定的某条记录。

4- 幻影读

一个事务中指定范围的两次查询结果因为其他事务更新了符合范围的数据,导致两次结果查询结果不同,这样的现象叫做幻影读。幻影读不仅仅只适用于符合条件的范围内的记录有多少,还适用于涉及到范围概念的情况。与范围数据有关。

三、锁

锁,即锁定,在数据库的概念为:在哪些数据或者对象上获取了锁就对对应的数据或者对象进行了锁定,其他事务就无法获取和现有锁相冲突的锁。锁是事务用来保护与自己交互的数据或者对象不受其他事务干扰的机制,实现了事务与事务之间的隔离。正是因为锁的存在,才可以根据业务需求合理地解决并发访问带来的问题。

1- 锁的粒度与锁升级

数据库可以在某一行获取锁,也可以对某一张表获取锁,也可以对整个数据库获取锁。这种多层次的锁结构成为锁的粒度。锁的粒度越粗,并发度越低,系统性能越高。
下面可以申请锁的粒度类型:

  • 行或行标识符(RID):属于行级锁(InnoDB),用于锁定堆中某个行的行标识符。
  • 键(Key):属于行级锁,在索引的键上存放锁,用户保护事务中的键的范围。
  • 页(Page):锁定该页中的所有数据或键。
  • 区(Excent):锁定整个区段,包括里面的页以及页中的数据行和键。
  • 表(Table):锁定整个表以及与表关联的所有对象,如表中的数据行、索引键。
  • 数据库(Database):锁定整个数据库。

由低层次的锁升级到高层次的锁成为锁升级。

2- 锁的类型

数据库引擎基于事务类型选择不同的锁,这些锁决定了并发事件访问资源的方式。此处只列出三种类型的锁。

(1)共享锁(S锁)
共享锁用于只需要读取不需要进行修改或更新数据的操作,如SELECT语句就是一种最基本常见的申请共享锁的语句。共享锁避免了不可重复读与幻影读问题。

(2)独占锁(X锁)
独占锁也成为排他锁。与其他所有的锁都冲突。当需要进行数据更改操作如INSERT、UPDATE、DELETE时,锁管理器就会分配X锁。一般情况下,数据修改时包含两个动作:读取需要的数据和修改数据,因此在数据修改时会申请共享锁和独占锁。在同一张表中修改数据,此时共享锁更应该成为更新锁,但是如果更新操作连接了其他表,那么其他表中就会存在共享锁,并在需要的数据上申请独占锁。

(3)更新锁(U锁)
更新锁和共享锁兼容,和独占锁冲突。更新锁和更新锁也冲突。修改数据时会先申请更新锁后申请独占锁, 更新锁是一种过渡锁。在进行数据搜索时持有了更新锁,由于更新锁和共享锁兼容,因此此时其他事务是允许读取数据的,当确定修改数据后,更新锁等待其他事务的共享锁释放后就会转换为独占锁(那么等待时间如何计算?),并将其他事务的相关资源的锁申请全部队列化堵在数据修改的进程外,直到独占锁释放,其他事务才能进行相关资源的申请。

  • 死锁:两个事务都在等待一个资源,但同时又相互阻止对方获取资源,这时就会发生死锁现象。例如,事务A和事务B都获取了某一行数据的共享锁(也就是可以查看该数据),当事务A想修改该数据时要将共享锁转化为独占锁,这就需要等待事务B释放共享锁,但是事务B也想修改数据,将共享锁转换为独占锁,它将等待事务A释放共享锁,这样两个事务之间形成了僵局。

更新锁和共享锁是兼容的,因此更新锁和共享锁可能在同一资源上相互共存,但是更新锁和更新锁是相互冲突的,所以只能有一个事务对数据有更新锁。在过度为独占锁前,只有更新锁的事务必须先等待其他事务释放所有的共享锁,这就避免了上述的死锁问题。

四、事务隔离级别

在数据库系统中可以通过设置事务隔离级别间接地控制锁,实现事务之间的隔离,从而解决并发问题。事务隔离级别是并发控制的整体解决方案,其实质是通过控制锁来控制事务之间如何进行隔离。

1- 提交读(READ COMMITTED)

查询申请的共享锁在语句执行完毕后就释放,不需要等待事务结束后释放;数据修改申请的独占锁一直持有,直到事务结束才释放。设置提交读,可以避免脏读问题,但是不能解决不可重复读和幻影读。

PS:设置事务隔离级别和查看。隔离级别的设置是对会话级别的,所以只对当前会话有效。

2- 可提交读(READ UNCOMMITTED)

未提交读是控制级别最低的级别,设置之后,该会话的所有读操作将不申请共享锁,因此读时将忽略所有的锁,但是更新时仍然会申请独占锁,这种情况下并发带来的问题都有可能发生。

3- 可重复读(REPEATABLE READ)

MySQL默认事务隔离级别,当设置为可重复读隔离级别时,除了独占锁会一直保持到事务结束,共享锁也一样到事务结束。可重复读隔离级别下,脏读、丢失的更新和不可重复读问题都能够避免,但是也因为共享锁一直持有,会导致其他事务不能对相关数据进行修改,降低了并发度和性能。可重复读隔离级别无法解决幻影读问题。

4- 串行化(SERIALIZABLE)

串行化隔离级别隔离层次最高,它能够避免丢失的更新、脏读、不可重复读和幻影读问题。设置为串行化隔离级别后,共享锁也将一直持有到事务结束。比可重复读更严格的是它的锁定是范围的,还包括潜在的数据修改。它保证了范围内两次查询结果不会出现增加记录或减少记录而出现幻象。

串行化隔离级别对锁控制的方式为:如果在查询指定条件的列上有索引,则在该列符合条件的范围记录上加上KEY粒度的锁,如果在查询条件的列上没有索引,则直接在表上加上共享锁。

五、隔离级别、锁和并发问题的关系

123
jonathan-fei

jonathan-fei

Stay hungry. Stay foolish.

26 posts
5 categories
46 tags
GitHub E-Mail Twitter Weibo
© 2018 jonathan-fei
Powered by Hexo
|
Theme — NexT.Pisces v5.1.3