Wetts's blog

Stay Hungry, Stay Foolish.

0%

转自:http://book.51cto.com/art/201112/307016.htm

_id和ObjectId

MongoDB 中存储的文档必须有一个”_id“键。这个键的值可以是任何类型的,默认是个 ObjectId 对象。在一个集合里面,每个文档都有唯一的”_id“值,来确保集合里面每个文档都能被唯一标识。如果有两个集合的话,两个集合可以都有一个值为 123 的”_id“键,但是每个集合里面只能有一个”_id“是 123 的文档。

ObjectId

ObjectId 是”_id“ 的默认类型。它设计成轻量型的,不同的机器都能用全局唯一的同种方法方便地生成它。这是MongoDB 采用ObjectId,而不是其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个服务器上同步自动增加主键值既费力还费时。MongoDB 从一开始就设计用来作为分布式数据库,处理多个节点是一个核心要求。后面会看到ObjectId 类型在分片环境中要容易生成得多。

ObjectId 使用 12 字节的存储空间,每个字节两位十六进制数字,是一个 24 位的字符串。由于看起来很长,不少人会觉得难以处理。但关键是要知道这个长长的 ObjectId 是实际存储数据的两倍长。

如果快速连续创建多个 ObjectId,会发现每次只有最后几位数字有变化。另外,中间的几位数字也会变化(要是在创建的过程中停顿几秒钟)。这是ObjectId 的创建方式导致的。12 字节按照如下方式生成:

1

ObjectId 生成规则

4字节:UNIX时间戳

前 4 个字节是从标准纪元开始的时间戳,单位为秒。这会带来一些有用的属性。时间戳,与随后的 5 个字节组合起来,提供了秒级别的唯一性。

由于时间戳在前,这意味着 ObjectId 大致会按照插入的顺序排列。这对于某些方面很有用,如将其作为索引提高效率,但是这个是没有保证的,仅仅是“大致”。

这 4 个字节也隐含了文档创建的时间。绝大多数驱动都会公开一个方法从 ObjectId 获取这个信息。

因为使用的是当前时间,很多用户担心要对服务器进行时间同步。其实没有这个必要,因为时间戳的实际值并不重要,只要其总是不停增加就好了(每秒一次)。

3 字节:表示运行 MongoDB 的机器

接下来的 3 字节是所在主机的唯一标识符。通常是机器主机名的散列值。这样就可以确保不同主机生成不同的 ObjectId,不产生冲突。

2 字节:表示生成此 _id 的进程

为了确保在同一台机器上并发的多个进程产生的 ObjectId 是唯一的,接下来的两字节来自产生 ObjectId 的进程标识符(PID)。

3 字节:由一个随机数开始的计数器生成的值

前 9 字节保证了同一秒钟不同机器不同进程产生的 ObjectId 是唯一的。后 3 字节就是一个自动增加的计数器,确保相同进程同一秒产生的 ObjectId 也是不一样的。同一秒钟最多允许每个进程拥有 2563(16 777 216)个不同的 ObjectId。

自动生成 _id

前面讲到,如果插入文档的时候没有”_id“键,系统会自动帮你创建一个。可以由 MongoDB 服务器来做这件事情,但通常会在客户端由驱动程序完成。理由如下。

虽然 ObjectId 设计成轻量型的,易于生成,但是毕竟生成的时候还是产生开销。在客户端生成体现了 MongoDB 的设计理念:能从服务器端转移到驱动程序来做的事,就尽量转移。这种理念背后的原因是,即便是像 MongoDB 这样的可扩展数据库,扩展应用层也要比扩展数据库层容易得多。将事务交由客户端来处理,就减轻了数据库扩展的负担。

在客户端生成 ObjectId,驱动程序能够提供更加丰富的 API。例如,驱动程序可以有自己的 insert 方法,可以返回生成的 ObjectId,也可以直接将其插入文档。如果驱动程序允许服务器生成 ObjectId,那么将需要单独的查询,以确定插入的文档中的”_id“值。

插入

db.collection.insert()

插入

存储在 MongoDB 集合中的每个文档(document)都有一个默认的主键 _id,这个主键名称是固定的,它可以是 MongoDB 支持的任何数据类型,默认是 ObjectId

查询

db.collection.find()

数组查询

名称 描述
$all 通过多个元素来匹配数组。
$size 查询特定长度的数组。
$slice 返回数组的一个子集合。

举例

db.food.find()

数据:

1
2
3
{ "_id" : "001", "fruits" : [ "苹果", "香蕉", "橘子" ] }
{ "_id" : "002", "fruits" : [ "苹果", "梨子", "橘子", "桃子" ] }
{ "_id" : "003", "fruits" : [ "圣女果", "梨子", "橘子", "桃子" ] }
$all

查询水果列表中既有苹果又有桃子的食品:db.food.find({"fruits":{"$all":["苹果","桃子"]}}) --这是AND的关系

结果:

1
{ "_id" : "002", "fruits" : [ "苹果", "梨子", "橘子", "桃子" ] }

查询水果列表中下标为1的元素是梨子的食品:db.food.find({"fruits.1":"梨子"})

结果:

1
2
{ "_id" : "002", "fruits" : [ "苹果", "梨子", "橘子", "桃子" ] }
{ "_id" : "003", "fruits" : [ "圣女果", "梨子", "橘子", "桃子" ] }
$size

数组包含3个元素的文档:db.food.find({"fruits":{"$size":3}})

结果:

1
{ "_id" : "001", "fruits" : [ "苹果", "香蕉", "橘子" ] }
$slice

返回lists后3个元素:db.test.find({},{"lists":{"$slice":-3}})

结果:

1
2
3
{ "_id" : "001", "fruits" : [ "苹果", "香蕉", "橘子" ] }
{ "_id" : "002", "fruits" : [ "梨子", "橘子", "桃子" ] }
{ "_id" : "003", "fruits" : [ "梨子", "橘子", "桃子" ] }

修改

语法

db.collection.update(<query>, <update>, upsert:<boolean>, multi:<boolean>)

参数 类型 描述
query document 要修改哪些的查询条件,类似于SQL 的 where
update document 要修改的字段对应的值
upsert boolean 可选的,默认值是false。如果根据查询条件没找到对应的文档,如果设置为true,相当于执行insert,如果设置为false,不做任何的操作。
multi boolean 可选的,默认值是false。如果根据查询条件找到对应的多条记录是,如果设置为false时,只修改第一条,如果设置为true,全部更新。

对单个字段进行修改

名称 描述
$inc 根据要添加的值递增该字段的值。
$mul 将该字段的值乘以指定的值
$rename 重命名字段
$setOnInsert 操作时,操作给相应的字段赋值
$set 用来指定一个键的值,如果不存在则创建它
$unset 用来指定一个键的值,如果不存在不创建创建它
$min 只有当指定的值小于现有字段值时才更新该字段。
$max 只有当指定的值大于现有字段值时才更新该字段。
$currentDate 设置当前日期字段的值,或者作为一个日期或时间戳。

对数组进行修改

名称 描述
$ 作为一个占位符的更新与查询条件在一个更新的第一要素
$addToSet 将元素添加到数组中,仅当它们在集合中不存在
$pop 删除数组的第一个或最后一个项
$pullAll 从数组中移除所有匹配值
$pull 移除匹配指定查询的所有数组元素
$pushAll 将所有值添加到数组中(Deprecated since version 2.4: Use the $push operator with $each instead.)
$push 将值添加到数组中,如果有的数组存在则向数组末尾添加该值,如果数组不存在则创建该数组并保存该值

删除

删除文档

db.collection.remove()

删除 orders 集合的数据,集合还存在,索引都还存在

删除集合

db.collection.drop()

集合、索引都不存在了

$elemMatch 是用来匹配数组内的元素的。

. 容易产生混淆。

$elemMatch. 对比

初始化源数据

1
2
3
4
5
6
db.em.insert({"arr":[{"e1":"a","e2":11},{"e1":"b","e2":12},{"e1":"c","e2":13},{"e1":"d","e2":14}]})
db.em.insert({"arr":[{"e1":"a","e2":21},{"e1":"b","e2":22},{"e1":"c","e2":23},{"e1":"d","e2":24}]})
db.em.insert({"arr":[{"e1":"a","e2":31},{"e1":"b","e2":32},{"e1":"c","e2
":33},{"e1":"d","e2":34}]})
db.em.insert({"arr":[{"e1":"a","e2":41},{"e1":"b","e2":42},{"e1":"c","e2":43},{"e1":"d","e2":44}]})
db.em.insert({"arr":[{"e1":"a","e2":51},{"e1":"b","e2":52},{"e1":"c","e2":53},{"e1":"d","e2":54}]})

. 查询

db.em.find({"arr":{$elemMatch: {"e1":"a","e2":32}}})

无结果集

$elemMatch 查询

db.em.find({"arr.e1":"a","arr.e2":32})

查询结果如下:
1

总结

  • $elemMatch:数组内单个元素匹配则符合
  • .:数组内多个元素能分别匹配上条件即符合

在 mysql 中有一个 information_schema 数据库,这个数据库中装的是 mysql 的元数据,包括数据库信息、数据库中表的信息等。所以要想查询数据库占用磁盘的空间大小可以通过对 information_schema 数据库进行操作。

information_schema 中的表主要有:

  • schemata表:这个表里面主要是存储在 mysql 中的所有的数据库的信息
  • tables表:这个表里存储了所有数据库中的表的信息,包括每个表有多少个列等信息。
  • columns表:这个表存储了所有表中的表字段信息。
  • statistics表:存储了表中索引的信息。
  • user_privileges表:存储了用户的权限信息。
  • schema_privileges表:存储了数据库权限。
  • table_privileges表:存储了表的权限。
  • column_privileges表:存储了列的权限信息。
  • character_sets表:存储了 mysql 可以用的字符集的信息。
  • collations表:提供各个字符集的对照信息。
  • collation_character_set_applicability表:相当于 collations 表和 character_sets 表的前两个字段的一个对比,记录了字符集之间的对照信息。
  • table_constraints表:这个表主要是用于记录表的描述存在约束的表和约束类型。
  • key_column_usage表:记录具有约束的列。
  • routines表:记录了存储过程和函数的信息,不包含自定义的过程或函数信息。
  • views表:记录了视图信息,需要有 show view 权限。
  • triggers表:存储了触发器的信息,需要有 super 权限。

1
2
3
4
5
6
7
use information_schema;

select concat(round(data_length/1024/1024,2),'MB') as data_length_MB,
concat(round(index_length/1024/1024,2),'MB') as index_length_MB
from tables where
table_schema='<数据库名>'
and table_name = '<表名>';

查询的生命周期的下一步是将一个SQL转换成一个执行计划,MySQL再按照这个执行计划和存储引擎进行交互。这包括多个子阶段:解析SQL、预处理、优化SQL执行计划。这个过程中任何错误(例如语法错误)都可能终止查询。

语法解析器和预处理

首先,MySQL通过关键字将SQL语句进行解析,并生成一颗对应的“解析树”。MySQL解析器将使用MySQL语法规则验证和解析查询。例如,它将验证是否使用错误的关键字,或者使用关键字的顺序是否正确等,再或者它还会验证引号是否能前后正确匹配。

预处理器则根据一些MySQL规则进一步检查解析树是否合法,例如,这里将检查数据表和数据列是否存在,还会解析名字和别名,看看它们是否有歧义。

下一步预处理器会验证权限。这通常很快,除非服务器上有非常多的权限配置。

查询优化器

现在语法树被认为是合法的了,并且由优化器将其转化成执行计划。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。

MySQL使用基于成本的优化器,它将尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。最初,成本的最小单位是随机读取一个4K数据页的成本,后来(成本计算公式)变得更加复杂,并且引入了一些“因子”来估算某些操作的代价,如当执行一次WHERE条件比较的成本。可以通过查询当前会话的Last_query_cost的值来得知MySQL计算的当前查询的成本。

1
2
3
SELECT SQL_NO_CACHE COUNT(*) FROM sakila.file_actor;

SHOW STATUS LIKE 'Last_query_cost';

有很多原因会导致MySQL优化器选择错误的执行计划

  • 统计信息不准确。MySQL以来存储引擎提供的统计信息来评估成本,但是有的存储引擎提供的信息是准确的,有的偏差可能非常大。例如,InnoDB因为其MVCC的架构,并不能维护一个数据表的行数的精确统计信息。
  • 执行计划中的成本估算不等同于实际执行的成本。所以即使统计信息精准,优化器给出的执行计划也可能不是最优的。例如有时候某个执行计划虽然需要读取更多的页面,但是它的成本却更小。因为如果这些页面都是顺序读或者这些页面都已经在内存中的话,那么它的访问成本将很小。MySQL层面并不知道哪些页面在内存中,哪些在磁盘上,所以查询实际执行过程中到底需要多少次物理I/O是无法得知的。
  • MySQL的最优可能和你想的最优不一样。你可能希望执行时间尽可能的短,但是MySQL只是基于其成本模型选择最优的执行计划,而有些时候这并不是最快的执行方式。所以这里我们看到根据执行成本来选择执行计划并不是完美的模型。
  • MySQL从不考虑其他并发执行的查询,这可能会影响到当前查询的速度。
  • MySQL也并不会任何时候都是基于成本的优化。有时也会基于一些固定的规则,例如,如果存在全文搜索的MATCH()子句,则在存在全文所以的时候就使用全文索引。即使有时候使用别的索引和WHERE条件可以远比这种方式要快,MySQL也仍然会使用对应的全文索引。
  • MySQL不会考虑不受其控制的操作的成本,例如执行存储过程或者用户自定义函数的成本。
  • 后面我们还会看到,优化器有时候无法去估算所有可能的执行计划,所以它可能错过实际上最优的执行计划。

静态优化和动态优化

MySQL的查询优化器是一个非常复杂的部件,它使用了很多优化策略来生成一个最优的执行计划。优化策略可以简单地分为两种,一种是静态优化,一种是动态优化。

  • 静态优化可以直接对解析树进行分析,并完成优化。例如,优化器可以通过一些简单的代数变换将WHERE条件转换成另一种等价形式。静态优化不依赖于特别的数值,如WHERE条件中带入的一些常数等。静态优化在第一次完成后就一直有效,即使使用不同的参数重复执行查询也不会发生变化。可以认为这是一种“编译时优化”。
  • 动态优化则和查询的上下文有关,也可能和很多其他因素相关,例如WHERE条件中的取值、索引中条目对应的数据行数等。这需要在每次查询的时候都重新评估,可以认为这是“运行时优化”。

在执行语句和存储过程的时候,动态优化和静态优化的区别非常重要。MySQL对查询的静态优化只需要一次,但对查询的动态优化则在每次执行时都需要重新评估。有时候甚至在查询的执行过程中也会重新优化(例如,在关联操作中,范围检查在执行计划会针对每一行重新评估索引)。

下面是一些MySQL能够处理的优化类型

重新定义关联表的顺序

数据表的关联并不总是按照在查询中指定的顺序进行。决定关联的顺序是优化器很重要的一部分功能。

将外连接转化成内连接

并不是所有的OUTER JOIN语句都必须以外连接的方式执行。诸多因素,例如WHERE条件、库表结构都可能会让外连接等价于一个内连接。MySQL能够识别这点并重写查询,让其可以调整关联顺序。

使用等价变换规则

MySQL可以使用一些等价变换来简化并规范化表达式。它可以合并和减少一些比较,还可以移除一些恒成立和一些恒不成立的判断。例如:(5=5 AND a>5)将被改写为a>5。类似的,如果有(a<b AND b=c) AND a=5则会改写为b>5 AND b=c AND a=5

优化COUNT()、MIN()和MAX()

索引和列是否可为空通常可以帮助MySQL优化这类表达式。例如,要找到某一列的最小值,只需要查询对应B-Tree索引最左端的记录,MySQL可以直接获取索引的第一行记录。在优化器生成执行计划的时候就可以利用这一点,在B-Tree索引中,优化器会将这个表达式作为一个常数对待。类似的,如果要查找一个最大值,也只需读取B-Tree索引的最后一条记录。如果MySQL使用了这种类型的优化,那么EXPLAIN中就可以看到“Select tables optimized away”。从字面意思可以看出,它表示优化器已经从执行计划中移除了该表,并以一个常数取而代之。

类似的,没有任何WHERE条件的COUNT(*)查询通常也可以使用存储引擎提供的一些优化(例如,MyISAM维护了一个变量来存放数据表的行数)。

预估并转化为常数表达式

当MySQL检测到一个表达式可以转化为常数的时候,就会一直把该表达式作为常数进行优化处理。例如,一个用户自定义变量在查询中没有发生变化时就可以转换为一个常数。

在优化阶段,有时候甚至一个查询也能转化为一个常数。一个例子是在索引列上执行MIN()函数。甚至是主键或者唯一键查找语句也可以转换为常数表达式。如果WHERE子句中使用了该类索引的常数条件,MySQL可以在查询开始阶段就先查找到这些值,这样优化器就能够直到并转换为常数表达式。

覆盖索引扫描

当索引中的列包含所有查询中需要使用的列的时候,MySQL就可以使用索引返回需要的数据,而无须查询对应的数据行。

子查询优化

MySQL在某些情况下可以将子查询转换一种效率更高的形式,从而减少多个查询多次对数据进行访问。

提前终止查询

在发现已经满足查询需求的时候,MySQL总是能够立刻终止查询。一个典型的例子就是当使用了LIMIT子句的时候。除此之外,MySQL还有几类情况也会提前终止查询,例如发现了一个不成立的条件,这时MySQL可以立刻返回一个空结果。除此之外,MySQL在执行过程中,如果发现某些特殊的条件,则会提前终止查询

等值传播

如果两个列的值通过等式关联,那么MySQL能够把其中一个列的WHERE条件传递到另一列上。

列表IN()的比较

在很多数据库系统中,IN()完全等同于多个OR条件的子句,因为这两者是完全等价的。在MySQL中这点是不成立的,MySQL将IN()列表中的数据先进行排序,然后通过二分查找的方式来确定列表中的值是否满足条件,这是一个O(log n)复杂度的操作,等价地转换成OR查询的复杂度为O(n),对于IN()列表中有大量取值的时候,MySQL的处理速度将会更快。

数据和所有的统计信息

在服务器层有查询优化器,却没有保存数据和索引的统计信息。统计信息由存储引擎实现,不同的存储引擎可能会存储不同的统计信息(也可以按照不同的格式存储统计信息)。某些引擎,例如Archive引擎,则根本就没有存储任何统计信息!

因为服务器层没有任何统计信息,所以MySQL查询优化器在生成查询的执行计划时,需要向存储引擎获取相应的统计信息。存储引擎则提供给优化器对应的统计信息,包括:每个表或者索引有多少个页面、每个表的每个索引的基数是多少、数据行和索引长度、索引的分布信息等。优化器根据这些信息来选择一个最优的执行计划。

MySQL如何执行关联查询

MySQL中“关联”一词所包含的意义比一般意义上理解的要更广泛。总的来说,MySQL认为任何一个查询都是一次“关联”——并不仅仅是一个查询需要到两个表匹配才叫关联,所以在MySQL中,每一个查询,每一个片段(包括子查询、甚至基于单表的SELECT)都可能是关联。

当前MySQL关联执行的策略很简单:MySQL对任何关联都执行嵌套循环关联操作,即MySQL先在一个表中循环取出单条数据,然后在嵌套循环到下一个表中寻找匹配的行,依次下去,直到找到所有表总匹配的行为止。然后根据各个表匹配的行,返回查询中需要的各个列。MySQL会尝试在最后一个关联表中找到所有匹配的行,如果最后一个关联表无法找到更多的行以后,MySQL返回到上一层次关联表,看是否能够找到更多的匹配记录,一次类推迭代执行。

执行计划

和很多其他关系数据库不同,MySQL并不会生成查询字节码来执行查询。MySQL生成查询的一颗指令树,然后通过存储引擎执行完成这颗指令树并返回结果。最终的执行计划包含了重构查询的全部信息。

MySQL执行计划是一颗左侧深度优先的树。

关联查询优化器

MySQL优化器最重要的一部分就是关联查询优化,它决定了都个表关联时的顺序。

排序优化

无论如何排序都是一个成本很高的操作,所以从性能角度考虑,应尽可能避免排序或者尽可能避免对大量数据进行排序。

当不能使用索引生成排序结果的时候,MySQL需要自己进行排序,如果数据量小则在内存中进行,如果数据量大则需要使用磁盘,不过MySQL将这个过程统一称为文件排序(filesort),即使完全是内存排序不需要任何磁盘文件时也是如此。

如果需要排序的数据量小于“排序缓冲区”,MySQL使用内存进行“快速排序”操作。如果内存不够排序,那么MySQL会先将数据分块,对每个独立的块使用“快速排序”进行排序,并将各个块的排序结果存放到磁盘上,然后将各个排好序的块进行合并(merge),最后返回排序结果。

MySQL有如下两种排序法:

两次传输排序(旧版本使用)

读取行指针和需要排序的字段,对其进行排序,然后再根据排序结果读取所需要的数据行。

这需要进行两次数据传输,即需要从数据表中读取两次数据,第二次读取数据的时候,因为是读取排序列进行排序后的所有记录,这会产生大量的随机I/O,所以两次数据传输的成本非常高。当使用的是MyISAM表的时候,成本可能会更高,因为MyISAM使用系统调用进行数据的读取(MyISAM非常依赖操作系统对数据的缓存)。不过这样做的优点是,在排序的时候存储尽可能少的数据,这就让“排序缓冲区”中可能容纳尽可能多的行数进行排序。

单次传输排序(新版本使用)

先读取查询所需要的所有列,然后在根据给定列进行排序,最后直接返回排序结果。这个算法只在MySQL 4.1和后续更新的版本才引入。因为不再需要从数据表中读取两次数据,对于I/O密集型的应用,这样做的效率高了很多。另外,相比两次传输排序,这个算法只需要一次顺序I/O读取所有的数据,而无须任何的随机I/O。缺点是,如果需要返回的列非常多、非常大,会额外占用大量的空间,而这些列对排序操作本身来说是没有任何作用的。因为单条排序记录很大,所以可能会有更多的排序块需要合并。

很难说哪个算法效率更高,两种算法都有各自最好和最糟的场景。当查询需要所有列的总长度不超过参数max_length_for_sort_data时,MySQL使用“单次传输排序”,可以通过调整这个参数来影响MySQL排序算法的选择。

当向MySQL发送一个请求的时候,MySQL的执行流程:

  1. 客户端发送一条查询给服务器。
  2. 服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。
  3. 服务器进行SQL解析、预处理,再由优化器生成对应的执行计划。
  4. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。
  5. 将结果返回给客户端。

  • show dbs:显示数据库列表
  • show collections:显示当前数据库中的集合(类似关系数据库中的表)
  • show users:显示用户
  • use :切换当前数据库,这和MS-SQL里面的意思一样
  • db.help():显示数据库操作命令,里面有很多的命令
  • db.foo.help():显示集合操作命令,同样有很多的命令,foo指的是当前数据库下,一个叫foo的集合,并非真正意义上的命令
  • db.foo.find():对于当前数据库中的foo集合进行数据查找(由于没有条件,会列出所有数据)
  • db.foo.find( { a : 1 } ):对于当前数据库中的foo集合进行查找,条件是数据中有一个属性叫a,且a的值为1

创建数据库

MongoDB 没有创建数据库的命令,但有类似的命令。

如:如果你想创建一个“myTest”的数据库,先运行 use myTest 命令,之后就做一些操作(如:db.createCollection('user')),这样就可以创建一个名叫“myTest”的数据库。

删除数据库

db.dropDatabase();

从指定主机上克隆数据库

db.cloneDatabase("127.0.0.1");将指定机器上的数据库的数据克隆到当前数据库

从指定的机器上复制指定数据库数据到某个数据库

db.copyDatabase("mydb", "temp", "127.0.0.1");将本机的mydb的数据复制到 temp 数据库中

修复当前数据库

db.repairDatabase();

查看当前使用的数据库

  • db.getName();
  • db;
    db和getName方法是一样的效果,都可以查询当前使用的数据库

显示当前db状态

db.stats();

当前db版本

db.version();

查看当前db的链接机器地址

db.getMongo();

在Linux中查看内存使用量用free命令

1
free -m

-m的意思是用m字节来显示

total used free shared buffers cached
Mem: 3829 2682 1146 0 174 840
-/+ buffers/cache: 1667 2161
Swap: 0 0 0
  • Mem
    • total:内存总数
    • used:已经使用的内存数
    • free:空闲的内存数
    • shared:主要用于在UNIX 环境下不同进程之间共享数据,是进程间通信的一种方法,一般的应用程序不会申请使用共享内存
    • buffers:系统分配但未被使用的buffers 数量
    • cached:系统分配但未被使用的cache 数量
  • -/+ buffers/cache
      • buffers/cache:实际使用的buffers 与cache 总量,也是实际使用的内存总量。(指的第一部分Mem行中的used – buffers – cached)
      • buffers/cache:未被使用的buffers 与cache 和未被分配的内存之和,这就是系统当前实际可用内存。(指的第一部分Mem行中的free + buffers + cached)

cache 和 buffer的区别

Cache

高速缓存,是位于CPU与主内存间的一种容量较小但速度很高的存储器。由于CPU的速度远高于主内存,CPU直接从内存中存取数据要等待一定时间周期,Cache中保存着CPU刚用过或循环使用的一部分数据,当CPU再次使用该部分数据时可从Cache中直接调用,这样就减少了CPU的等待时间,提 高了系统的效率。Cache又分为一级Cache(L1 Cache)和二级Cache(L2 Cache),L1 Cache集成在CPU内部,L2 Cache早期一般是焊在主板上,现在也都集成在CPU内部,常见的容量有256KB或512KB L2 Cache。

把读取过的数据保存起来,重新读取时若命中(找到需要的数据)就不要去读硬盘了,若没有命中就读硬盘。其中的数据会根据读取频率进行组织,把最频繁读取的内容放在最容易找到的位置,把不再读的内容不断往后排,直至从中删除。

Buffer

缓冲区,一个用于存储速度不同步的设备或优先级不同的设备之间传输数据的区域。通过缓冲区,可以使进程之间的相互等待变少,从而使从速度慢的设备读入数据时,速度快的设备的操作进程不发生间断。

根据磁盘的读写设计的,把分散的写操作集中进行,减少磁盘碎片和硬盘的反复寻道,从而提高系统性能。linux有一个守护进程定 期清空缓冲内容(即写如磁盘),也可以通过sync命令手动清空缓冲。举个例子吧:我这里有一个ext2的U盘,我往里面cp一个3M的MP3,但U盘的 灯没有跳动,过了一会儿(或者手动输入sync)U盘的灯就跳动起来了。卸载设备时会清空缓冲,所以有些时候卸载一个设备时要等上几秒钟。


  • buffer: 作为buffer cache的内存,是块设备的读写缓冲区
  • cache: 作为page cache的内存, 文件系统的cache

如果 cache 的值很大,说明cache住的文件数很多。如果频繁访问到的文件都能被cache住,那么磁盘的读IO bi会非常小。

很多高性能的应用都会对关联查询进行分解。简单地,可以对每一个表进行一次单表查询,然后将结果在应用程序中进行关联。

1
2
3
4
SELECT * FROM tag
JOIN tag_post ON tag_post.tag_id=tag.id
JOIN post ON tag_post.post_id=post.id
WHERE tag.tag='mysql';

可以分解成下面这些查询来代替。

1
2
3
SELECT * FROM tag WHERE tag='mysql';
SELECT * FROM tag_post WHERE tag_id=1234;
SELECT * FROM post WHERE post.id in (123,456,567,9098,8904);

用分解关联查询的方式重构查询有如下的优势:

  • 让缓存的效率更高。许多应用程序可以方便地缓存单表查询对应的结果对象。例如,上面查询中的tag已经被缓存了,那么应用就可以跳过第一个查询。再例如,应用中已经缓存了ID为123、567、9098的内容,那么第三个查询的IN()中就可以少几个ID。另外,对MySQL的查询缓存来说,如果关联中的某个表发生了变化,那么就无法使用查询缓存了,而拆分后,如果某个表很少改变,那么基于该表的查询就可以重复利用查询缓存结果了。
  • 将查询分解后,执行单个查询可以减少锁的竞争。
  • 在应用层做关联,可以更容易对数据库进行拆分,更容易做到高性能和可扩展。
  • 查询本身效率也可能会有所提升。这个例子中,使用IN()代替关联查询,可以让MySQL按照ID顺序进行查询,这可能比随机的关联要更高效。
  • 可以减少冗余数据的查询。在应用层做关联查询,意味着对于某条记录应用只需要查询一次,而在数据库中做关联查询,则可能需要重复访问一部分数据。从这点看,这样的重构还可能会减少网络和内存的消耗。
  • 更进一步,这样做相当于在应用中实现了哈希关联,而不是使用MySQL的嵌套循环关联。某些场景哈希关联的效率要高很多。

很多场景下,通过重构查询将关联放到应用程序中将会更加高效,这样的场景有很多,比如:当应用能够方便地缓存单个查询的结果的时候、当可以将数据分布到不同的MySQL服务器上的时候、当能够使用IN()的方式代替关联查询的时候、当查询中使用同一个数据表的时候。