标签 mysql 下的文章

第一步:创建sqlite文件

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

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

第二步:连接mysql

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

第三步:数据同步

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

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

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

使用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;

一、索引类型

索引根据底层实现可分为B-Tree索引和哈希索引,大部分时候我们使用的都是B-Tree索引,因为它良好的性能和特性更适合于构建高并发系统。

根据索引的存储方式来划分,索引可以分为聚簇索引非聚簇索引。聚簇索引的特点是叶子节点包含了完整的记录行,而非聚簇索引的叶子节点只有所以字段和主键ID。

根据聚簇索引和非聚簇索引还能继续下分还能分为普通索引、覆盖索引、唯一索引以及联合索引等。

- 阅读剩余部分 -

一、为什么要使用索引

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

《高性能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多版本并发控制

一、行锁和两阶段锁协议

行锁:顾名思义,就是对某一行加锁,修改的时候不会锁住整个表。相对于表锁来说,行锁的开销更大(因为涉及到MVCC等需要保存快照),但是粒度更小,更适合于高并发场景。行锁是每个引擎单独实现的,但是并不是所有的引擎都实现了行锁。例如MyISAM就没有实现行锁,它只支持表锁,而InnoDB支持行锁,因此高并发场景基本都会选择InnoDB作为数据库引擎。

行锁是系统自动加上的,无需手动干预。当多个事务同时更新一行数据时,后来事务的更新操作会被阻塞,直到行锁被释放才能更新。而行锁并不是在事务一启动就加上了,而是在真正需要的时候才加锁,也就是说只有在更新的时候才对行加锁。这样做有两个好处:

  1. 减小锁的粒度和加锁时长,提高并发度。
  2. 事务启动的时候,并没有明确说明需要修改什么行,此时如果如果要锁定行必须锁定整个表才行。

和加锁时机不同,行锁不是在更新完行之后立马就解锁,而是在事务执行完成(执行了commit或者rollback)之后才解锁。这两个加锁的时机被称为两阶段锁协议

例如以下表包含了学生信息:

# 创建表
CREATE TABLE stu_info  (
  `id` int(0) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT '',
  `age` tinyint(0) UNSIGNED NOT NULL DEFAULT 20,
  PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8;
# 插入三行数据
insert into stu_info(name, age) values('maqian', 24), ('zed', 10), ('yasuo', 15);

启动两个事务,同时修改id为2的学生的年龄,其中事务一先修改但不提交,事务二也修改同一行内容,事务二就会被阻塞。具体的执行流程为:

首先,第一个事务执行修改:

事务1

然后,第二个事务也执行修改:

image.png

此时事务二被阻塞,因此这一条记录已经被事务一锁定了,要等待事务一释放锁后才能修改。

一直到一段时间过去后事务二会弹出报错,意思时等待锁超时了:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

此时事务二虽然不能修改id为2的记录,但是可以修改其他id的记录:

这说明,两个事务都只是对各自修改的记录行加锁了,并没有对整个表加锁。两个事务执行完成后的表:

+----+--------+-----+
| id | name   | age |
+----+--------+-----+
|  1 | maqian |  24 |
|  2 | zed    |  16 |
|  3 | yasuo  |  18 |
+----+--------+-----+

行锁变表锁

当行锁涉及的索引失效时,会导致行锁变成表锁。例如上面的示例中修改条件都是id = 2,因为id是主键,默认会生成索引。因此两个事务更新记录时使用的是行锁,事务一锁住id = 2的行的时候,事务二还是能更新id = 3的行。

但是一旦把条件改成其他列,即where后面的条件改成其他(如where name 'zed')时,行锁会升级成表锁。即使事务一只修改name = 'zed'的列,事务二也无法修改name = 'yasuo'这一列。

二、死锁

在事务中,多个事务间同时对不同的行加锁,可能会导致死锁。例如以下场景:

事务一先修改id = 3的用户的年龄并锁住该行,然后事务二修改id = 2的年龄也锁住这行。接着事务一修改id = 2的用户年龄,因为这一行已经被事务二锁住了,所以这条语句会被阻塞,要等到事务二释放锁才能继续。但是此时事务二想修改id = 3的用户的年龄,刚好这行又被事务一给锁住。两个事务互相都要等待对方先释放锁,产生了死锁。

这个场景比较类似《unix环境高级编程》中对死锁产生原因的描述:当多个线程以不当的顺序同时对多个锁加锁就会导致死锁。

死锁产生后,MySQL有两种方法来解决死锁:

  1. 等待锁超时。如上面测试的一样,当更新语句等待锁一段时间后会超时退出,不会无休止等待下去。但是这个超时时间默认是50S,太长了,在高并发系统中是无法接受的。
  2. 设置死锁检测。通过设置innodb_deadlock_detect = on,开启MySQL的死锁检测,系统会自动检测死锁的事务并回滚改动。

大部分时候使用的都是方式二,主动检测死锁来释放锁,因为方式一超时时间太长了。但是方式二也有缺点是检测时间是O($n^2$)。例如,有100个事务在执行的时候,每个事务执行的时候都要和另外99个线程检测是否存在死锁。此时需要执行10000次死锁检测,当事务的数量再上升的时候,死锁的检测又会上升一个量级。

如何解决这个问题呢?一般有以下几种办法:

  1. 合理规划数据表的执行顺序,尽量避免多个事务以不同顺序更新同一个表。如果都是相同的顺序访问,是不会产生死锁的。这种情况下,可以关闭死锁检测。
  2. 控制并发量,在代码中限制同时执行事务的数量,控制在10以内,超过的排队执行。这样就减少了死锁检测的次数,虽然有排队的事务,但是排队的时间远远小于多个事务之间的死锁检测时间。

一、概述

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表同样会锁全表。

一、备份数据库

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';

一、SELECT介绍

1.1 SELECT

SELECT是数据库四大基本操作的一种,用于查询表中的数据信息。

基本的查询语法为:SELECT 列1, 列2, ... FROM 表,表示从表中取出对应的列。

SELECT语句的用法多种多样,并且还有很多高级的操作(如排序、分组以及联合等等),是增删改查四种基本操作中用法最多也运用最广的命令。

1.2 测试数据

创建测试表stu_info,所有的测试将会在这张表上进行:

CREATE TABLE `stu_info` (
  `stu_id` int(12) unsigned zerofill NOT NULL AUTO_INCREMENT,
  `stu_name` varchar(255) NOT NULL DEFAULT '',
  `age` tinyint(4) NOT NULL,
  `classno` tinyint(4) NOT NULL,
  `city` varchar(255) NOT NULL,
  PRIMARY KEY (`stu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

表中数据:

+--------------+-----------+-----+---------+----------+
| stu_id       | stu_name  | age | classno | city     |
+--------------+-----------+-----+---------+----------+
| 000000000001 | maqian    |  24 |       1 | changsha |
| 000000000002 | xiaoming  |  19 |       2 | shanghai |
| 000000000003 | xiaohua   |  23 |       2 | shenzhen |
| 000000000004 | xiaobai   |  22 |       3 | shenzhen |
| 000000000005 | xiaowang  |  19 |       4 | hunan    |
| 000000000006 | xiaozhou  |  20 |       3 | wuhan    |
| 000000000007 | xiaoli    |  20 |       1 | changsha |
| 000000000008 | xiaopeng  |  23 |       1 | changsha |
| 000000000009 | xiaozheng |  22 |       1 | fujian   |
+--------------+-----------+-----+---------+----------+

二、使用SELECT查询数据

2.1 查询单列数据

查询所有的学生名字:

SELECT stu_name FROM stu_info;

结果:

+-----------+
| stu_name  |
+-----------+
| maqian    |
| xiaoming  |
| xiaohua   |
| xiaobai   |
| xiaowang  |
| xiaozhou  |
| xiaoli    |
| xiaopeng  |
| xiaozheng |
+-----------+
注意事项:查询时,如果没有明确指定排序对象,返回的数据中顺序没有特殊意义,每次返回的顺序可能都不同。只要保证每次返回的行数是一样的就是正常。

2.2 查询多个列

查询所有学生的名字及年龄信息,查询多列时,不同列之间使用,隔开:

SELECT stu_name, age FROM stu_info;

结果:

+-----------+-----+
| stu_name  | age |
+-----------+-----+
| maqian    |  24 |
| xiaoming  |  19 |
| xiaohua   |  23 |
| xiaobai   |  22 |
| xiaowang  |  19 |
| xiaozhou  |  20 |
| xiaoli    |  20 |
| xiaopeng  |  23 |
| xiaozheng |  22 |
+-----------+-----+

2.3 查询所有列

使用通配符*表示查询所有列:

SELECT * FROM stu_info;

结果:

+--------------+-----------+-----+---------+----------+
| stu_id       | stu_name  | age | classno | city     |
+--------------+-----------+-----+---------+----------+
| 000000000001 | maqian    |  24 |       1 | changsha |
| 000000000002 | xiaoming  |  19 |       2 | shanghai |
| 000000000003 | xiaohua   |  23 |       2 | shenzhen |
| 000000000004 | xiaobai   |  22 |       3 | shenzhen |
| 000000000005 | xiaowang  |  19 |       4 | hunan    |
| 000000000006 | xiaozhou  |  20 |       3 | wuhan    |
| 000000000007 | xiaoli    |  20 |       1 | changsha |
| 000000000008 | xiaopeng  |  23 |       1 | changsha |
| 000000000009 | xiaozheng |  22 |       1 | fujian   |
+--------------+-----------+-----+---------+----------+
使用*来输出所有列时,会严重降低检索和应用程序的性能。大部分时候,尽量少使用用通配符,明确自己所需要的列。

2.4 去除重复数据

在查询时,可以通过DISTINCT关键字来剔除重复的行。如查询所有的班级(不重复):

SELECT DISTINCT classno FROM stu_info;

结果:

+---------+
| classno |
+---------+
|       1 |
|       2 |
|       3 |
|       4 |
+---------+

当对多个关键字使用DISTINCT时,只有所有列都相同才会被认为是重复的,其中某个字段相同并不会认为是同一个行:

SELECT DISTINCT classno, city FROM stu_info;

上面的数据中,有多个classno=1并且city=changsha的结果,使用DISTINCT之后,这些重复的行被剔除了,而同样classno=但是city=fujian的记录却依然存在:

+---------+----------+
| classno | city     |
+---------+----------+
|       1 | changsha |
|       2 | shanghai |
|       2 | shenzhen |
|       3 | shenzhen |
|       4 | hunan    |
|       3 | wuhan    |
|       1 | fujian   |
+---------+----------+

2.5 限制结果

使用LIMIT关键字可以限制输出的结果数量,语法格式为:

  1. LIMIT N:只输出前面N条记录。
  2. LIMIT M,N:从第M条记录开始,输出N条记录。
  3. LIMIT N OFFSET M:MYSQL从5.0开始支持的语法,作用和第二条一样,从M开始输出N条记录。
第三种用法实际上是为了解决用户会混淆M,N究竟是从M开始的N条记录还是N开始的M条记录的问题。

2.5.1 显示前面5条记录

SELECT classno, city FROM stu_info LIMIT 5;

结果:

+---------+----------+
| classno | city     |
+---------+----------+
|       1 | changsha |
|       2 | shanghai |
|       2 | shenzhen |
|       3 | shenzhen |
|       4 | hunan    |
+---------+----------+

2.5.2 从第5条之后显示5条记录

SELECT classno, city FROM stu_info LIMIT 5, 5;

结果:

+---------+----------+
| classno | city     |
+---------+----------+
|       3 | wuhan    |
|       1 | changsha |
|       1 | changsha |
|       1 | fujian   |
+---------+----------+

当实际的记录数量小于剩余记录时,输出的结果并不会达到我们想要的行数。

使用SELECT classno, city from stu_info LIMIT 4 OFFSET 5的结果也和上面一样!

三、对结果排序

排序是查询是最常用的的功能之一,语法格式为ORDER BY col1, col2,表示根据col1col2排序。

MYSQL支持对单行和多行数据排序,也支持正序和倒序排序。默认情况是正序排序,逆序排序需要手动添加关键字DESC

3.1 对单列数据排序

输出学生的年龄、班级和名字,并针对年龄排序:

SELECT age, stu_name FROM stu_info ORDER BY age;

结果:

+-----+---------+-----------+
| age | classno | stu_name  |
+-----+---------+-----------+
|  19 |       2 | xiaoming  |
|  19 |       4 | xiaowang  |
|  20 |       3 | xiaozhou  |
|  20 |       1 | xiaoli    |
|  22 |       3 | xiaobai   |
|  22 |       1 | xiaozheng |
|  23 |       2 | xiaohua   |
|  23 |       1 | xiaopeng  |
|  24 |       1 | maqian    |
+-----+---------+-----------+

可以看到,age列都是从小到大排列,而classno还是处于无序的状态。

3.2 对多列数据排序

在上面的基础上,添加对班级排序逻辑。即当学生年龄一致的时候,根据所在的班级排序:

SELECT age, classno, stu_name FROM stu_info ORDER BY age, classno;

结果:

+-----+---------+-----------+
| age | classno | stu_name  |
+-----+---------+-----------+
|  19 |       2 | xiaoming  |
|  19 |       4 | xiaowang  |
|  20 |       1 | xiaoli    |
|  20 |       3 | xiaozhou  |
|  22 |       1 | xiaozheng |
|  22 |       3 | xiaobai   |
|  23 |       1 | xiaopeng  |
|  23 |       2 | xiaohua   |
|  24 |       1 | maqian    |
+-----+---------+-----------+

结果中,所有年龄相同的行,班级序号也是有序的。

3.3 逆序排序

逆序输出所有的学生名字:

SELECT stu_name FROM stu_info ORDER BY stu_name DESC;

结果:

+-----------+
| stu_name  |
+-----------+
| xiaozhou  |
| xiaozheng |
| xiaowang  |
| xiaopeng  |
| xiaoming  |
| xiaoli    |
| xiaohua   |
| xiaobai   |
| maqian    |
+-----------+

四、其他

4.1 使用完全限定的表名

查询时,可以明确查询的表名和列名,如:

SELECT stu_info.stu_name from stu_info;
注意:不可省略最后的表信息,不要认为列中限定了表名最后就不用再添加表名了。

效果等同于:

SELECT stu_name FROM stu_info;

这种用法一般适用于多表之间的联合查询,当两个表中的字段有重合时,需要明确指定表名来限定查询的是哪个表中的字段。