分类 数据库 下的文章

第一步:创建sqlite文件

点击左上角的连接按钮,选择sqlite,在弹出的窗口中选择新建sqlite3,然后设置好各项配置:

最后选择保存即可创建sqlite文件

第二步:连接mysql

再次点击左上角按钮连接,选择mysql,在弹出的窗口中配置好连接信息:

第三步:数据同步

在工具栏,点击工具-数据传输,左边选择mysql连接和数据库,右边选择sqlite连接和数据库:

注意:传输到sqlite中的数据库使用默认的main就好了,不要

然后一直选择下一步就行了,之后执行完成后就会生成sqlite文件了。

一、基本用法

字符串是redis中的基本数据类型之一,能存储任何形式的字符串,包括二进制数据。同时,它还可以进行字符串运算数据运算位运算等操作。一个字符串最大能有512M。

字符串主要的操作命令有两个:

  • GET KEY: 如果KEY存在就返回对应的值,如果不存在则返回空值nil
  • SET KEY VALUE: 给KEY设置值为VALUE,如果KEY已经存在则更新值。

例如:

# 设置一个新值
127.0.0.1:6379> set test HelloWorld
OK
# 获取值
127.0.0.1:6379> get test
"HelloWorld"
# 获取不存在的值返回nil
127.0.0.1:6379> get aaaa
(nil)

字符串也支持同时给多个key同时赋值,方法为:

  • MGET key value [key value]: 同时设置多个键值。
  • MSET key value [key value]: 同时获取多个键值。
127.0.0.1:6379> mset keya aaa keyb bbb keyc ccc
OK
127.0.0.1:6379> mget keya keyb keyc
1) "aaa"
2) "bbb"
3) "ccc"

还有一个常用的操作就是在获取key的同时并设置key的值:

  • GETSET key value:给key赋值并返回先前的元素,如果元素不存在返回nil
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> getset k 123
(nil)
127.0.0.1:6379> getset k 456
"123"

二、操作API

字符串主要有以下操作命令:

2.1. APPEND

在尾部增加字符串,命令格式:append key value,成功将会返回添加后的字符串长度。

127.0.0.1:6379> set test Hello
OK
127.0.0.1:6379> append test World
(integer) 10  # 添加成功返回总长度
127.0.0.1:6379> get test
"HelloWorld"

2.2. STRLEN

获取字符串长度,命令格式:strlen key,成功将会返回该值得长度。

127.0.0.1:6379> strlen test
(integer) 10
127.0.0.1:6379> strlen aaaa
(integer) 0  # 键不存在返回0

2.3. GETRANGE

获取指定偏移范围内的字符,命令格式:getrange key start end,键不存在返回空。

127.0.0.1:6379> getrange test 5 10
"World"
127.0.0.1:6379> getrange aaaa 1 2
""  # 键不存在返回空

和大多数程序语言一样,redis的字符串下标从0开始,到len(key) - 1结束。要注意的是redis中可以使用-1表示最后一位。

127.0.0.1:6379> getrange test 0 -1 # 获取第一个字符到最后一个字符
"HelloWorld"

三、数据运算

当我们存入一个十进制整数或者浮点数到redis当中去的时候,redis会自动察觉到这一点,并允许我们使用相关的命令来操作它们。

3.1 INCR和DECR

把整形数据加一或者减一,命令格式:incr key decr key,执行成功会返回增加过后的值,如果key不存在时会自动创建。

127.0.0.1:6379> set num 100
OK
127.0.0.1:6379> incr num
(integer) 101  
127.0.0.1:6379> incr num
(integer) 102
127.0.0.1:6379> decr num
(integer) 101
127.0.0.1:6379> get num
"101"
127.0.0.1:6379> incr aaaa
(integer) 1  # 自动创建aaaa并初始化值为0然后+1

3.2 INCRBY、DECRBY和INCRBYFLOAT

增加或者删除指定的大小,命令格式为:incrby key incrementINCRBYDECRBY针对整数,INCRBYFLOAT针对浮点数,不可以使用INCRBYDECRBY操作浮点数。

127.0.0.1:6379> set num 100
OK
127.0.0.1:6379> incrby num 10
(integer) 110
127.0.0.1:6379> decrby num 5
(integer) 105
127.0.0.1:6379> incrbyfloat num 1.1
"106.1"
127.0.0.1:6379> incrby num 10
(error) ERR value is not an integer or out of range  # 使用整形命令操作浮点数会报错

使用数据运算的同时还可以字符串命令:

127.0.0.1:6379> set num 100
OK
127.0.0.1:6379> append num 999  # 在尾部添加
(integer) 6
127.0.0.1:6379> get num
"100999"
127.0.0.1:6379> incr num  # 仍然可以执行数据运算
(integer) 101000

除了这些以外,redis还支持位运算,可参考:Redis中的位运算

四、实现原理

字符串内部使用了三种编码方式,分别是int/raw/embstr。

4.1 int编码

当存储的字符串是一个整形数据的时候,redis会自动以整形数据来保存。

127.0.0.1:6379> set mobile 10086
OK
127.0.0.1:6379> OBJECT ENCODING mobile
"int"

当使用int类型编码的时候,字符串对象结构为:

4.2 raw和embstr编码

raw和embstr编码底层都是使用sdshdr来存储字符串,使用raw方式编码的字符串对象结构:

embstr和raw编码的不同在于使用embstr编码的时候,redis会把上面的redisObject和shshdr作为一个对象同时申请内存,它们在内存上是连续的,而raw编码的字符串在内存上并不连续。

当存储的字符串长度小于39时,redis会使用embstr来编码。这样做的目的主要有以下几个:

  1. 减少内存的分配和释放次数
  2. 内存连续适合小对象缓存

使用mysql连接远程服务器时报错,在百度和google查找都没有找到能解决问题的办法:

ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 0

分析应该是以下两个原因导致的:

  1. 服务器有防火墙,禁止3306端口的访问。
  2. 用户没有授权远程访问。

第二个错误首先被排除掉了,根据多年的经验来看,如果是没有权限报错应该是Access Deny或者Permission相关的错误,但是这个错误从没见过。

因此排查的重心就放在了防火墙上了,首先在服务端查看防火墙,防火墙是关闭的状态,并且3306端口允许所有主机访问:

说明不是第一种场景导致的。那么问题来了,这到底是个什么奇葩错误?没办法,只能上终极大招了——抓包。

使用tcpdump抓包:

tcpdump -i eth0 host 192.168.123.17 and tcp port 3306 -nnv -c 100 -w 3306.pcap

然后放到本地用wireshark打开,一个明显的错误就映在眼前了:

1130,没有权限访问,说明还是用户没有权限访问服务器导致:

气到吐血!不知道为什么没有权限客户端是这种鬼错误,直接打印服务端返回来的错误不就行了吗?wtf!

这是一台内网的虚拟机设备,root用户没有开外网访问权限,很久没有使用了不记得了。

解决办法

给root权限加上外网访问权限:

GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456';
FLUSH PRIVILEGES;

当使用的内存到达上限后,redis提供了6种策略来淘汰键值:

策略描述
volatile-lru在所有设置了过期时间的键值中根据LRU算法淘汰最近最少使用的
allkeys-lru对数据库中所有元素根据LRU算法淘汰最近最少使用的
volatile-random从设置了过期时间的元素中随机淘汰
allkeys->random数据库所有元素中随机淘汰
volatile-ttl从设置了过期时间的键值中淘汰快要超时的
noeviction不淘汰任何已有键值,直接给写操作返回错误
LRU是最近最少使用的,直译出来就是最久没有使用的。

redis默认的淘汰策略是volatile-lru,修改淘汰策略可以通过修改redis.conf文件中的maxmemory-policy字段,配置中关于各种淘汰策略也有详细的解释。使用grep volatile-lru redis.conf -A 6 -n可以过滤出这部分配置 :

一、为什么要使用索引

索引是存储引擎用于快速找到记录的一种数据结构。索引对于数据库良好的性能十分关键,尤其是表中的数据量越来越大时,索引对性能的影响十分明显。

《高性能MySQL》中对索引的评价是:索引优化应该是对查询性能优化最有效的手段了,索引能够轻而易举将查询性能提高几个数量级。

以innodb为例,innodb中存储数据的基本元素是,页里面保存了许多数据记录,各个记录通过链表串联起来。一个innodb页的结构为:

innodb给每个页分配了16KB的大小,除了存储用户记录以外还有一些额外的字段没有展示出来。用户记录并不是一定装满了整个页,因此除了用户记录以外还有一部分未使用的空间,后续的新纪录可以继续插入到未使用空间中。

除了页内的记录用链表串起来了之外,每个页面也是通过链表连接起来的:

试想正常情况下,如果想要找到一条记录应该怎么找呢?首先要遍历所有的页面,然后遍历页面里面的记录,一条条记录对比,找到需要查询的记录。这样的话时间复杂度是O(N),N代表总的记录条数。

如果数据库中只有几条或者几十条记录,查起来或许还行。但是如果数据库有几千万甚至上亿条记录,这么查起来是什么样子?MySQL数据是写在磁盘上的,一次磁盘寻址所需要的时间是10ms,如果有1亿条记录,那么执行一次查询需要10亿毫秒,也就是1百万秒,算下来需要11.5天。这种级别的耗时是任何一个业务系统都无法忍受的。

而索引存在的意义就在于此,通过特定的结构来排布整个数据库,使得系统能在较快的时间内查询到记录。索引就像是一本书的目录,告诉你哪一章在哪一页,想看对应的章节直接放到对应页数就可以了。

一个最简单的索引思路是:把所有的记录排序,通过二分查找的方式来查找元素,查询的时间复杂度是O(logN)。这样的话1亿条记录,只需要20多次查询就可以了,算下来时间不到1秒,相比之前的11天已经不是一个数量级了。当然,实际的索引实现也不仅仅是二分查找这么简单。

最常用的索引有两种:

  1. B-Tree索引,基于B+树结构的索引。
  2. 哈希索引,基于哈希表实现的索引。

大部分时候,使用的都是B-Tree索引。

二、B-Tree索引

B-Tree索引是一种基于B+树结构的索引,B+树因为其独特的结构优势所以被广泛应用于索引中:

  1. 一个节点包含了多个数据域,适应于操作系统成块访问磁盘的特性,可以一次读取多个节点的数据。
  2. 相对于B树来说,B+树非叶子节点不包含任何数据,只包含子节点指针 ,因此一个节点所能指向的子节点个数更多,这样的话B+树会更矮,查询起来更高效。

一个B-Tree索引的结构为(橙色是数据域,绿色是子节点指针):

如果想要找到id等于32的记录,首先通过页1定位到子页10,然后继续查找页10,定位到页31,最终找到32。

可以看出,查找的效率是与B+树的层数相关的,树越高,查找效率越慢,树越低,查找效率越快。实际的应用中,一个页远远不止上面展示的3个记录项,按照一行记录100字节来算,一页数据(16K)至少可容纳1500个记录,那么1亿条记录只需要三层树(10^9<1500*1500*1500)。也就是说,1亿条数据最多执行三次IO就能定位到,可见其效率之高。

索引除了可以按值查找以外,还支持对ORDER BY子句的排序,只要排序字段也正确匹配上了索引就可以。

B-Tree支持的索引匹配条件:

  1. 全部匹配:支持同时匹配多个索引。
  2. 部分匹配:支持同时匹配多个索引中的部分索引。
  3. 匹配列前缀:对添加了索引的列,可以匹配其左前缀。例如匹配maqian中的前缀ma。
  4. 匹配范围:支持对索引列去范围值。

三、哈希索引

哈希索引是一种基于哈希表的索引结构,它是一种需要精确匹配才生效的索引结构。

实现原理:对索引列计算哈希值把记录映射到哈希槽中,然后指向对应记录行的地址。因此,在查询的时候只要正确匹配到索引列,就能在O(1)的时间复杂度内查到记录。

以下是一个哈希索引的示例,左边是哈希槽,右边是对应的数据列:

相比于B-Tree索引而言,哈希索引有不少的局限性:

  • 哈希索引不支持排序
  • 哈希索引不支持部分列索引查找
  • 哈希索引只支持等值查询,无法提供范围查询功能

哈希索引的查找效率是非常高的,大多数时候都能在O(1)的时间内找到记录,除非哈希冲突很高。

innodb中有一个内建功能叫自适应哈希,当存储引擎注意到有列频繁访问的时候,就会建立对应的哈希索引。这样,引擎就同时拥有了B-Tree索引和哈希索引,就能使用更加快速的查找。这是一个无需人工干预的自动行为。

哈希索引使用的场景

哈希索引常见的一种场景是针对长字符串查询的优化,例如数据库中保存了大量的URL信息,查询URL中不可能一个字符一个字符去搜索,这样效率太低。

这种情况就可以使用哈希索引:给所有的URL计算一个crc保存起来,然后对crc做哈希索引。查询的时候指定crc和url就能快速定位到记录了。如:

select * from url_info where crc = xxxx and url = "http://www.baidu.com"

执行这条语句的时候,会先针对crc查找哈希索引,找出所有crc值等于xxxx的记录,过滤掉大多数不符合条件的记录。然后再根据后面的url信息详细匹配,这样查询效率就很高了。

四、索引的缺点

所有的优点是查询速率很快,但同时也有缺点。

索引的主要缺点是会导致插入和更新语句变慢,因为每次更新数据都要重新维护索引,索引越多,耗时越长。

同时,如果建立了不恰当的索引可能还会导致数据库性能更低,这个就依赖人工的操作了。

一、概述

事务的出现给并发带来了巨大的便利性,它的ACID特性使得数据在并发时更加可靠。但是对于事务而言,它也会导致出现第一类丢失更新第二类丢失更新脏读不可重复读以及幻读的问题,当然又出现了多种事务隔离级别来避免在产生这几类问题。那么隔离级别是如何实现的呢?

这就是多版本并发控制(MVCC)要做的事情了。《高性能MySQL》中对MVCC的描述为:

  • MySQL的大多数事务性存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制。不仅是MySQL,包括Oracle、PostgreSQL等其他数据库系统也都实现了MVCC,但各自的实现机制不尽相同,因为MVCC没有一个统一的实现标准。
  • 可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只锁定必要的行。
  • MVCC的实现,是通过保存数据在讴歌时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始时间的不同,每个事务同一张表、同一时刻看到的数据可能是不一样的。

MVCC的核心功能点是快照,多个事务更新相同数据时,各自都会生成一份对应数据的快照,这个快照被称为一致性读视图(consistent read view)。有了这个视图之后,每个事务都只对自己内部的视图进行更改,这样就不会影响其他事务了,数据并发就不会受到影响。

问题的关键点就在于快照如何创建以及如何把多个事务的更改统一起来。

因为MyISAM不支持事务,因此本篇文章主要讨论的是InnoDB。

二、MVCC基本原理

首先第一个问题:快照是如何创建的?是不是就是给每个事务都拷贝一份数据呢,是不是有100G数据,那每个事务也要拷贝100G数据呢?当然不是。

每个事务都有一个事务ID叫做transaction id,这个id在事务刚启动的时候向InnoDB申请,它不重复并且严格递增。InnoDB隐藏了一个包含最新改动的事务id,每个事务修改后都会把这个字段设置为自己的事务ID。其他事务启动的时候记录下这个最新ID,然后修改的时候比对ID是否有修改。如果没有修改,说明这一行没有改动过,当前事务也能直接修改。如果ID变化了,则就要查找undolog,找到可用的合适的记录。

因此,创建快照就只要记录下这个事务ID就可以了,无需复制所有的数据。

在实现上,InnoDB给每个数据表都添加了隐藏的三列数据DB_TRX_ID/DB_ROLL_PTR/DB_ROW_ID,三者的含义:

  1. DB_TRX_ID: 标记了最新更新这条行记录的transaction id,每处理一个事务,其值自动+1。
  2. DB_ROLL_PTR: 回滚指针,记录了最新一次修改该条记录的undo log,回滚的时候就通过这个指针找到undo log回滚。
  3. DB_ROW_ID: 当数据表没有指定主键时,数据库会自动以这个列来作为主键,生成聚集索引。

每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务ID(即DB_TRX_ID列)。同时,旧的数据版本要保留(通过undo log保留),并且在新的数据版本中,能够有信息可以直接拿到它。也就是说,数据表中的一行数据,其实可能有多个版本。

例如存在以下数据表:

它实际上的表现形式为:

假设此时修改年龄为25,此时数据列和undo log的状态是:

undolog新生成了一个记录,保存了改动之前的数据。新记录中,通过设置DB_ROLL_PRT指向备份的undo log记录,方便回滚。如若再次修改年龄为18,那么两者的状态为:

三、MVCC具体过程

以下通过一个示例来描述MVCC具体的执行过程:

关于start transaction with consistent snapshot:

前面我们说过,行锁的加锁实际并不是事务启动的时候就创建的,而是在修改对应行的时候才创建的。这里的一致性视图默认情况下和视图也是一样,用到的时候才创建,并非事务已启动就创建。

start transaction with consistent snapshot语句的作用就是在启动事务的时候就创建一致性视图。

以上面的学生表为例,同时存在三个事务修改同一行数据中的值,其中事务A和事务B先执行,然后事务C更新数据并提交。此刻事务A和事务B的更新和查询记录的结果会是怎样?

初始时的行数据为:

idnameage
1maqian24

测试

首先启动启动两个终端模拟事务A和事务B,执行start transaction with consistent snapshot

然后开启第三个终端模拟事务C,执行:

mysql> update stu_info set age = age + 1 where name = 'maqian';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

然后在事务B执行以下操作:先查询age的值,然后也把age加一,然后再查询age的值。

事务B在更新操作执行之前,查询age的值是24,执行更新操作之后,再查询age值是26。

此时事务A查询age的值是24:

因此能得到的结论是:

  1. 事务B在更新数据前查到的age = 24,更新后查到的age = 25。
  2. 事务A查询到的数据是24。

看起来不符合逻辑,为什么会这样呢?

3.1 查询逻辑

我们以trx_id_first/trx_id_last/trx_id_currennt分别表示事务的低水位、高水位和当前事务。

如何理解高低水位:

  • 低水位:已经提交事务的最大值,即启动当前事务时候已经提交了的事务。
  • 高水位:未开始事务的最小值,即当前事务启动时还未启动的事务。
  • 当前事务:高低水位之间的事务,即当前事务启动时候已经存在的未提交事务。

以图形表示为:

图片来源:极客时间

在事务开始的时候,除了生成一致性视图,还要生成一个对应的视图数组,这个数组里面表示的就是所有未提交事务的集合(黄色区域)。查询数据的时候有三种情况:

  1. 数据未提交,数据不可见。
  2. 数据已提交,但是事务ID处于当前事务的高水位段,不可见。
  3. 数据已提交,并且事务ID实在当前事务之前创建,可见。

以上面的测试过程为例,事务A的id等于1,事务B的id等于2,事务C的id等于3。那么事务启动时,事务A的视图数组为[0, 1],事务B的视图数组为[0, 1, 2],事务C的视图数组为[0, 1, 2, 3]

事务C最后启动,因为是自动提交,因此执行完update之后就已经commit了,此时记录的DB_TRX_ID = 3。它处于事务B的高水位区,虽然已经提交但是也不可见,命中第二条规则。因此它要先通过undo log找到一个可见的版本,找到上一个版本trx_id = 0位于它的可见区,然后读取这条记录的age值为24.

而事务B对于事务A而言,也是处于高水位区,并且事务B修改age之后没有提交,所以rtx_id = 2的事务对事务A是不可见的,命中了规则一,要往前找其他版本。先找到上一个版本trx_id = 3后发现还不是可见的,需要继续往前找,找到rtx_id = 0的记录,它对事务A可见,再读取age值为24。

3.2 更新逻辑

上面有一个不合逻辑的点在于事务B,事务B它在更新age的前后分别查询age的值是对不上的:加一之前是24,加一之后是26。这是个什么逻辑呢?

对于更新而言,它有一个很重要的概念是当前读,当前读的意思是:更新的时候,要使用当前版本的记录来读。所谓当前版本指的就是最新更新后的记录,在上面的例子中也就是trx_id = 3的记录。

在事务B修改age的值之前,此时读取age值就像上面所说:先找到trx_id = 3的记录,发现不可见,然后再读取trx_id = 0的记录。

但是事务B在修改age的时候,先读取当前是最新的改动trx_id = 3这条记录,此时age的值为25。然后事务C把age值加一,并设置trx_id = 2。事务C再读取数据,发现最新的改动事务id是2,也就是自己。处于未提交的当前事务区,就能读到age是26了。

四、参考

MySQL-InnoDB-MVCC多版本并发控制

一、概述

binlog/redolog/undolog都是msql中的日志模块,其中二进制日志是mysql服务层实现的,redolog和undolog是引擎层实现的。binlog一般被称为二进制日志(也成为归档日志),redolog成为重做日志,undolog称为回滚日志。

binlog记录的数据库记录的改动日志,如:记录ID = 2这条记录的字段A加1,它主要用户数据的同步和复制。redolog记录的是物理层面的改动日志,如:记录某个扇区的某个字节修改成了1,它主要用于数据重做。undolog和binlog差不多,也是记录的逻辑日志,它主要用于MVCC中记录回滚。

redolog和undolog只存在于innodb中,myisam引擎并没有实现,这两个日志在innodb中统称为事务日志。但要注意的是,虽然undolog和redolog都能恢复数据,但undolog并不是redolog的逆向操作。undolog用于回滚,redolog用于前滚。

关于前滚和回滚:

前滚:事务提交之后,部分数据写入了磁盘,但是还有部分数据存在脏页上,并没有写入磁盘。此时设备宕机,没有写入磁盘的数据丢失。就要依赖redolog来恢复这部分数据。

回滚:事务还未提交,改动并没有完全生效,但是记录已经被修改。此时设备宕机,数据是有问题的,就要依赖undolog回滚改动。

大致的工作模型为:

二、二进制日志(bin log)

二进制日志是server层(即mysql)实现的,不用引擎单独再实现。它记录了所有对数据修改的过程,属于逻辑日志。例如当我们把某个字段增加了1,那么binlog就会记录一条日志,它是直接写入到文件系统的。binlog的主要用途是复制和同步数据,在多台设备间保持数据一致。

binlog只会保存对数据存在更改的记录,像select/show这类查询类的语句是不记录的。

开启二进制日志的方法:

[mysqld]
log-bin=[on|filename]

my.cnf文件中添加上对应的配置段即可,可以手动指定文件名。重启服务后生效。

当开启二进制日志后,mysql数据目录就会生成对应的数据文件:

binlog

也可以在mysql中通过指令得到文件相关信息,如:

SHOW {BINARY | MASTER} LOGS # 查看使用了哪些日志文件
SHOW BINLOG EVENTS [IN 'log_name'] [FROM pos] # 查看日志中进行了哪些操作
SHOW MASTER STATUS # 显示主服务器中的二进制日志信息

三、重做日志(redolog)

redolog是工作在物理层,它的作用主要是为了减少磁盘开销。因为磁盘操作是极为耗时的,因此,不可能每次对数据的更改都直接写入磁盘。redolog的作用就是缓存起来这些数据改动(缓存到磁盘),等缓存到达一定的数量后再统一写磁盘。

redolog是引擎层实现的,MyISAM没有实现这个功能,而InooDB实现了。

一个很生动形象的例子是《孔乙己》,在这篇文章中,有记录到老板赊账的过程:酒店掌柜有一个粉板,专门用来记录客人的赊账记录。如果赊账的人不多,那么他可以把顾客名和账目写在板上。但如果赊账的人多了,粉板记不下了,这个时候掌柜一定还有一个专门记录赊账的账本。如果有人要赊账或者还账的话,掌柜一般有两种做法:

  1. 直接把账本翻出来,把这次赊的账加上去或者扣除掉;
  2. 另一种做法是先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。

在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作实在是太麻烦了。首先,你得找到这个人的赊账总额那条记录。你想想,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写回到账本上。

redolog的作用实际上也就是老板赊账一样,为了提高效率,先提前把修改记录到一块地方,等到修改达到一定数量后再写入磁盘,减少磁盘写入次数。

为什么会减少磁盘写入次数呢?

假设redolog记录了一条日志,是说把某个扇区的值更新为3,然后后面又来了一条日志,要修改这个扇区的值为5,那么在这种情况下,只要把5写入到磁盘就行了。

redolog和binlog的对比

  1. redolog写入的是对磁盘的更改,binlog写入的是对记录行的修改。
  2. redolog是一个环形缓冲区,有大小限制,缓冲区满后把数据写入磁盘。而binlog是直接记录文件,没有文件大小限制, 大小超出后重新生成一个文件继续记录。
  3. redolog是引擎层实现,binlog是server层实现。

四、回滚日志(undo log)

回滚日志主要的用途是多版本并发控制中的回滚,多个事务同时更新数据时会生成回滚日志:

图片来源:极客时间《MySQL实战45讲》

其中U1/U2/U3表示的就是回滚日志,当记录要从V4回滚到V1时,要先依次通过U3和U2回滚到V2,再通过U1回滚到V1。

四、关于二阶段提交

二阶段提交的意思是:redolog和binlog都写入了之后再提交数据,确保日志数据都正常了才写入磁盘。

以下是binlog和redolog的工作流程:

写入redolog后,事务处于prepare状态,然后写入binlog,再commit。

这样做的目的就是避免两个日志中的某一个没有被正确写入出现异常,一旦两个日志行为不一致,后续的同步和恢复数据就不准确。

使用binlog和redolog恢复数据

如何使用binlog和redolog来恢复数据呢,一般是以下几个过程:

  1. 找到最近一次的全量备份
  2. 从备份的时间点开始执行binlog,重放到需要的时间点。

一、区别

区别一:MyISAM是非事务安全的,InnoDB是事务安全的

  • 事务安全的特点为更安全,遇到问题会自动恢复或从备份加事务日志回复,如果更新失败,你的所有改变都变回原来。
  • 非事务安全的优点为更快,所需的磁盘空间更小,执行更新时需要的内存更小,但是所有发生的改变都是永久的。
InnoDB用于事务处理,具有ACID(原子性,一致性,隔离性,持久性)事务支持等特性,如果在应用中大量使用insert和update操作,应选用InnoDB。

区别二:MyISAM锁的粒度是表级的,而InnoDB支持行级锁

数据库引擎具有多粒度锁定,允许一个事务锁定不同类型的资源。 为了尽量减少锁定的开销,数据库引擎自动将资源锁定在适合任务的级别。 锁定在较小的粒度(例如行)可以提高并发度,但开销较高,因为如果锁定了许多行,则需要持有更多的锁。 锁定在较大的粒度(例如表)会降低了并发度,因为锁定整个表限制了其他事务对表中任意部分的访问。 但其开销较低,因为需要维护的锁较少。

区别三:MyISAM支持全文类型索引,而InnoDB(以前)不支持全文索引

  • 主键索引:主键是一种唯一性索引,但它必须指定为PRIMARY KEY,每个表只能有一个主键。
  • 唯一索引:索引列的所有值都只能出现一次,即必须唯一,值可以为空。
  • 普通索引:基本的索引类型,值可以为空,没有唯一性的限制。
  • 全文索引:全文索引的索引类型为FULLTEXT。全文索引可以在varchar、char、text类型的列上创建。可以通过ALTER TABLE或CREATE INDEX命令创建。对于大规模的数据集,通过ALTER TABLE(或者CREATE INDEX)命令创建全文索引要比把记录插入带有全文索引的空表更快。MyISAM支持全文索引,InnoDB在mysql5.6之后支持了全文索引。

区别四:InnoDB支持外键,MyISAM不支持

区别五:MyISAM表保存成文件形式,跨平台使用更加方便

二、应用场景

MyISAM是MySQL默认的引擎,但是它没有提供对数据库事务的支持,也不支持行级锁和外键,因此当INSERT(插入)或UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。不过和Innodb不同,MyISAM中存储了表的行数,于是SELECT COUNT(*) FROM TABLE时只需要直接读取已经保存好的值而不需要进行全表扫描。同时MyISAM提供高速存储和检索以及全文搜索能力,如果在应用中执行大量select操作,应该选择MyISAM。如果表的读操作远远多于写操作且不需要数据库事务的支持,那么MyISAM也是很好的选择。

Innodb引擎提供了对数据库ACID事务的支持,并且实现了SQL标准的四种隔离级别。该引擎还提供了行级锁和外键约束,它的设计目标是处理大容量数据库系统,它本身其实就是基于MySQL后台的完整数据库系统,MySQL运行时Innodb会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎不支持FULLTEXT类型的索引,而且它没有保存表的行数,当SELECT COUNT(*) FROM TABLE时需要扫描全表。当需要使用数据库事务时,该引擎当然是首选。由于锁的粒度更小,写操作不会锁定全表,所以在并发较高时,使用Innodb引擎会提升效率。但是使用行级锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表。

一、三范式概念

第一范式:当关系模式R的所有属性都不能再分解为更基本的数据单位时,称R是满足第一范式的,简记为1NF。

第二范式:如果关系模式R满足第一范式,并且R得所有非主属性都完全依赖于R的每一个候选关键属性,称R满足第二范式,简记为2NF。

第三范式:设R是一个满足第一范式条件的关系模式,X是R的任意属性集,如果X非传递依赖于R的任意一个候选关键字,称R满足第三范式,简记为3NF。

二、理解三范式

2.1 第一范式

第一范式要求:

  • 每一列的属性都是不可再分的属性
  • 属性相近或者相似的,尽量合并成同一列

例如保存一个学生对象的信息时,学生信息表的设计:

这里的地址信息是可以拆分的:xx省 xx市 xx区,按照第一范式的准则,应该设计为:

2.2 第二范式

第二范式要求每一行的数据只与一列数据有关,数据中不能出现重复的项目,如果有就要把表拆开来。

如学生所在的班级信息:

表中可能会出现多个学生对应的班级信息都是一样的情况,此时class_id就会重复。应该把class_id和学生信息单独抽出来放在新表中。

2.3 第三范式

第三范式要求所有的列都跟主键有直接关系而不是间接关系。例如学生所在的班级和班级所在的楼栋地址等关系,不应该设计成学生 - 班级 - 地址的关系,而应该是学生 - 班级班级 - 地址,两者相互独立,应该拆开。

一、备份数据库

mysql自带了数据库备份工具mysqldump可以很方便的对数据库进行备份:

mysqldump -u root -p --all-database > db.sql

以上命令就完成了一次数据备份,备份后的数据保存在文件 db.sql ,参数 --all-databases 是指备份所有数据库。

如果只想备份特定的数据库,通过参数 --database, -B 指定即可,也可以直接加在命令后面:

mysqldump -u root -p test > test.sql

这条命令就只备份test数据库,生成的test.sql文件即为数据库。

二、恢复数据库

恢复数据库使用mysql命令就可以完成,要注意的地方是恢复到数据库之前要求数据库必须存在:

mysql -u root -p test < test.sql

以上命令就表示把备份的数据库文件导入到数据库test中,如果test数据库不存在,会报错:

root@35c000f43aa6:/backup# mysql -u root -p test< test.sql 
Enter password: 
ERROR 1049 (42000): Unknown database 'test'

三、mysqldump用户权限问题

使用mysqldump进行数据备份时依赖账户密码和数据库的访问权限,如果使用正常的业务账号容易导致账号密码被泄露。根据权限最小化原则,一般建议为mysqldump建立单独的用户身份。

一个单独的mysqldump用户应该包含以下权限:

  1. 只有只读权限,不能修改数据库内容
  2. 只能本地用户登陆

创建一个符合以上条件的dumper用户:

create user dumper@'127.0.0.1' identified by '123456';
grant select on test.* to dumper@'127.0.0.1';
grant show view on test.* to dumper@'127.0.0.1';
grant lock tables on test.* to dumper@'127.0.0.1';
grant trigger on test.* to dumper@'127.0.0.1';