Mysql是一种关系型数据库,默认端口为3306。
事务是逻辑上的一组操作,要么都执行,要么都不执行。
-- 读未提交read-uncommitted;
-- 读已提交read-committed;
-- 可重复读repeatable-read;
-- 串行化 serializable
Mysql 使用的索引主要由BTree索引和哈希索引, 对于哈希索引来说,底层使用的数据结构就是哈希表。 因此在绝大多数需求为单挑记录查询时,可以选择哈希索引,查询性能最快;其余大部分场景,选择BTree 索引。
Mysql 中的BTree 索引使用的时 B树 中的 B+Tree, 但对于主要的两种存储引擎的实现方式是不同的。
当Mysql 单表记录数过大时,数据库的CRUD 性能会明显下降,一些常见的优化措施如下:
数据库连接本质就是一个 socket 的连接。可以把数据库连接池是看做是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重用这些连接。在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中。连接池还减少了用户必须等待建立与数据库的连接的时间。
mysql主备复制实现分成三个步骤:

事务进⾏过程中,每次sql语句执⾏,都会记录undo log和redo log,然后更新数据形成脏⻚,然后redo log按照时间或者空间等条件进⾏落盘,undo log和脏⻚按照checkpoint进⾏落盘,落盘后相应的redo log就可以删除了。此时,事务还未COMMIT,如果发⽣崩溃,则⾸先检查checkpoint记录,使⽤相应的redo log进⾏数据和undo log的恢复,然后查看undo log的状态发现事务尚未提交,然后就使⽤undo log进⾏事务回滚。事务执⾏COMMIT操作时,会将本事务相关的所有redo log都进⾏落盘,只有所有redo log落盘成功,才算COMMIT成功。然后内存中的数据脏⻚继续按照checkpoint进⾏落盘。如果此时发⽣了崩溃,则只使⽤redo log恢复数据。


#开启慢查询日志记录
mysql> set global slow_query_log=on;
Query OK, 0 rows affected (0.00 sec)
#查询时间超过0.1秒的sql语句会被记录
mysql> set global long_query_time=0.1;
Query OK, 0 rows affected (0.03 sec)
#记录慢查询日志的文件地址
mysql> set global slow_query_log_file="/var/lib/mysql/localhost-slow.log";
Query OK, 0 rows affected (0.04 sec)
#记录没有使用索引的查询
mysql> set global log_queries_not_using_indexes=on;
Query OK, 0 rows affected (0.00 sec)
show variables like 'slow_query_log';
show variables like 'long_query_time';
show variables like 'slow_query_log_file';
show variables like 'log_queries_not_using_indexes';
用 EXPLAIN 查看SQL执行计划。


type列
连接类型。一个好的sql语句至少要达到range级别。杜绝出现all级别
key列
使用到的索引名。如果没有选择索引,值是NULL。可以采取强制索引方式
key_len列
索引长度
rows列
扫描行数。该值是个预估值
extra列
详细说明。注意常见的不太友好的值有:Using filesort, Using temporary

比如这个sql是否全表扫描了,索引是否正常生效(前提是表建了索引),对sql进行一个优化。
选择MySQL的隔离级别取决于应用程序的需求和性能考虑。一般来说,可以按照以下原则选择隔离级别:
大事务和长事务是两个不同的概念,它们分别关注事务的不同属性:
总的来说,大事务关注的是事务操作的数据量,而长事务关注的是事务的持续时间。两者都可能对数据库性能产生负面影响,需要根据实际情况采取不同的优化策略。

主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。
连接器负责跟客户端建立连接、获取权限、维持和管理连接。
在完成经典的 TCP 握手后,连接器就要开始认证你的身份,
如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。
这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。用户的权限表在系统表空间的mysql的user表中。
连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 show processlist 命令中看到它。文本中这个图是 show processlist 的结果,其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接。

客户端如果长时间不发送command到Server端,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值
是 8 小时。

mysql常用命令
mysql>show databases; 显示所有数据库
mysql>use dbname; 打开数据库:
mysql>show tables; 显示数据库mysql中所有的表;
mysql>describe user; 显示表mysql数据库中user表的列信息);
连接建立完成后,你就可以执行 select 语句了。执行逻辑就会来到第二步:查询缓存。
MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。
如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。
大多数情况查询缓存就是个鸡肋,为什么呢?
因为查询缓存往往弊大于利。查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。
一般建议大家在静态表里使用查询缓存,什么叫静态表呢?就是一般我们极少更新的表。比如,一个系统配置表、字典表,那这张表上的查询才适合使用查询缓存。好在 MySQL 也提供了这种“按需使用”的方式。你可以将my.cnf参数query_cache_type 设置成 DEMAND。
my.cnf
#query_cache_type有3个值 0代表关闭查询缓存OFF,1代表开启ON,2(DEMAND)代表当sql语句中有SQL_CACHE关键词时才缓存
query_cache_type=2
select SQL_CACHE * from test where ID=5;
这样这个sql查询的数据就会进入缓存。
show global variables like "%query_cache_type%";
show status like'%Qcache%'; //查看运行的缓存信息

Qcache_free_blocks:
表示查询缓存中目前还有多少剩余的blocks,如果该值显示较大,则说明查询缓存中的内存碎片过多了,可能在一定的时间进行整理。
Qcache_free_memory:
查询缓存的内存大小,通过这个参数可以很清晰的知道当前系统的查询内存是否够用,是多了,还是不够用,DBA可以根据实际情况做出调整。
Qcache_hits:
表示有多少次命中缓存。我们主要可以通过该值来验证我们的查询缓存的效果。数字越大,缓存效果越理想。
Qcache_inserts:
表示多少次未命中然后插入,意思是新来的SQL请求在缓存中未找到,不得不执行查询处理,执行查询处理后把结果insert到查询缓存中。这样的情况的次数,次数越多,表示查询缓存应用到的比较少,效果也就不理想。当然系统刚启动后,查询缓存是空的,这很正常。
Qcache_lowmem_prunes:
该参数记录有多少条查询因为内存不足而被移除出查询缓存。通过这个值,用户可以适当的调整缓存大小。
Qcache_not_cached:
表示因为query_cache_type的设置而没有被缓存的查询数量。
Qcache_queries_in_cache:
当前缓存中缓存的查询数量。
Qcache_total_blocks:
当前缓存的block数量。
mysql8.0已经移除了查询缓存功能
如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。
MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。
做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 from 写成了"rom"。
词法分析器原理
词法分析器分成6个主要步骤完成对sql语句的分析
1、词法分析
2、语法分析
3、语义分析
4、构造执行树
5、生成执行计划
6、计划的执行

SQL语句的分析分为词法分析与语法分析,
mysql的词法分析由MySQLLex[MySQL自己实现的]完成,语法分析由Bison生成。
那么除了Bison外,Java当中也有开源的词法结构分析工具例如Antlr4,ANTLR从语法生成一个解析器,可以构建和遍历解析树,可以在IDEA工具当中安装插件:antlr v4 grammar plugin。插件使用详见课程。
过bison语法分析之后,会生成一个这样的语法树

select * from test1 join test2 using(ID) where test1.name=yangguo and test2.name=xiaolongnv;
既可以先从表 test1 里面取出 name=yangguo的记录的 ID 值,再根据 ID 值关联到表 test2,再判断 test2 里面 name的值是否等于 yangguo。
也可以先从表 test2 里面取出 name=xiaolongnv 的记录的 ID 值,再根据 ID 值关联到 test1,再判断 test1 里面 name的值是否等于 yangguo。
这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。
> select * from test where id=1;
开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限)。
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
比如我们这个例子中的表 test 中,ID 字段没有索引,那么执行器的执行流程是这样的:
1:调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
2:调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
3:执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。也就是说如果我们在create table时不指定表的存储引擎类型,默认会给你设置存储引擎为InnoDB。
索引是帮助mysql高效获取数据的排好序的数据结构(Hash或者B+Tree)


可以看出来,就比如我们以自增id作为索引的时候,在比较极端的情况下,这不是二叉树了,变成一个链表了,和没有索引一样,也需要一条一条的去遍历查询,也需要对磁盘做很多的I/O(索引和数据都是存在磁盘上的),所以二叉树pass。

如上图红黑树看上去不错,但其实它的本质也是二叉树,只是加入了变色和自旋,在数据量比较大的情况下,树的高度不可控,也需要I/O很多次。所以红黑树pass。


如上图,索引的结点是存储在磁盘空间上的,不管是二叉树还是红黑树,一个节点是只存了一个索引的,而B-Tree呢就针对一个节点分配更大的空间,就可以在这个节点上面存储更多的索引元素(包含了索引和数据–指索引所在行的磁盘文件的地址),从而也能更好的控制树的高度,尽可能的做少的I/O次数。而且在此节点的磁盘空间中,对所有从左到有依次进行排序,便于更好的查找。


当节点中的数据大于我们规定值的15/16的时候,树的结构就会发生变化。
B+Tree叶节点只存储索引,不存储具体的数据(Data指索引所在行的磁盘文件的地址),数据只会存在叶子结点上,而且从左到右依次排序,支持双向查找。B+Tree会存储一些冗余的处于中间位置的索引,这样就可以在同样大的磁盘空间上存储更多的索引,提高查询效率,而且,每一行,索引都会从左到右依次递增。但是B-Tree叶节点即存储了索引也存储了起对应的内存地址,就会导致一个数据页存储的数据变少,当数据量过大的时候,树的高度也是很大的,所以最终选择了B+Tree。B+Tree相邻的叶子结点之间是有相互指向的(会存相邻叶子节点之间的内存地址),所以在范围查询的时候,效率也会更高。
除了以上优点,B+Tree还有很多优点,比如,每个父节点的值都小于等于他右子节点的值,大于他左子节点的值,这样一种左开右闭原则也能很好的提升查询的效率。
比如就上图我们要查找30这个索引,
1:从根节点出发,根节点是常驻内存的,首先将根节点加载到RAM内存中,在内存中对半查询查找,30在15-56之间,就将中间这块数据(其实就是下一级节点的磁盘文件地址-指针)取出加载到内存。30在20-49之间,就将这中间的节点取出加载到内存,然后就能从内存中定位到索引为30的元素,这元素下面就含有这个索引缩影的数据在磁盘中的内存地址。
SHOW GLOBAL STATUS LIKE 'Innodb_page_size'

如果数据库表很多,而且索引也不少,可能会把内存撑爆,而且同时把那么多数据加载到内存中去再去查到,速率也不会很快。而且一次磁盘I/O也加载不了这么多数据。而且根节点是常驻内存的。
数据库表主键索引,用bigint(8字节),(索引+下一级节点的磁盘文件地址-指针(mysql默认为6字节)),相加就14字节,16k/14~~1000多,第二层一样,第三层有叶子结点,含有data(不同的存储引擎存储结构不一样,后面再讲),就算一行数据有1kb,那么三层就可以存储1000x1000x16=16000000。而且mysql会提前将非叶子结点中的数据加载到内存中,相当于只需要做一次i/o就能定位到索引数据。
MyISAM与InnoDB是形容数据库表而不是数据库。
一个表主要由三个文件组成:见下图

sql执行的时候,会在表索引文件(B+Tree的数据结构存储索引)中,通过B+Tree的数据结构找到我们的索引,叶子结点下面存储的是存在表数据文件中的数据的地址,就可能达到对应的数据的内存地址,然后通过这个地址去表数据文件中定位获取到对应的数据。

一个表由两个文件组成,见下图


去表索引以及数据文件中查询到具体的索引后,就可以直接获取到具体的数据,不用再去其他文件查询了。
对于普通的索引,叶子节点存储的是主键id,然后更具主键再去主键索引中查询,查找具体数据。如果我们没有建主键,那么mysql就回去数据库中查找一列中数据唯一的字段作为主键,如果没有这样的数据,会自己生成rowid作为主键id。从节约空间以及通过比较大小快速查找索引角度考虑,主键建议使用自增的整型而不用其他的比如uuid。
在创建表的时候,ibd文件是按照B+Tree的数据结构来组织索引的,建立主键后,mysql就可以使用我们建立的主键来组织索引,如果我们我们没有建立自己的主键。那么mysql就会在我们的表中选择一个没有重复的数据的字段作为主键,如果没有这样的字段,mysql会自己生成一列隐藏的数据rowid来作为我们的主键,如果我们自己建立了主键,mysql就不用做后面这些工作了,可以提示mysql的性能,mysql的性能是很重要的,资源也是很宝贵的。所以我们最好自己建立个主键。
整型自增首先他是有序的,而且相对于uuid或者字符串,在生成B+Tree数据结构的时候,因为在一个数据页,从左到右,主键是逐渐变大的。更好比较大小,而且整型占用的空间相对而言要小一点。
在讨论这个问题之前先来看看hash索引。

hash会对存入来的索引数据进行一次hash运算,并且把这个值放入hash桶中,存在hash冲突的情况。
hash计算出索引的hash值后,一般情况下只需要进行一次磁盘I/O就能定位到数据,Hash相对于B+Tree效率很高,不出现Hash冲突的情况下,查询某一条数据时间复杂度为O(1),但是不支持范围查找,所以用的比较少。相反B+Tree叶子节点中是从左到右一次递增的,而且是个双向链表,不管是查询大于还是小于都很快,相邻的节点直接存有对方的磁盘文件地址,查找起来也很快。
回到为什么采用自增作为主键
B+Tree是排好序的。B+Tree叶子节点中是从左到右一次递增的,而且是个双向链表,不管是查询大于还是小于都很快,相邻的节点直接存有对方的磁盘文件地址,查找起来也很快。
如果非自增,在插入数据的时候可能会存在要向已经填满的节点里面强插数据,这样就可能会导致节点分裂,甚至还需要往父节点写入数据等等来平衡B+Tree,效率不如自增。

也是B+Tree的数据结构,

联合索引首先在构建B+Tree的数据结构的时候,排序的时候,会从左到右一次比较字段的大小去进行排序,第一个字段开始排序,如果第一个字段一样就用第二个字段进行比较排序,依次类推。
在查询的时候,遵循最左前缀原则,我们查询的时候,需要用联合索引的第一个字段来开始查,不能直接跳过这个字段去使用到其他的索引。假如这三个字段是联合索引,在我们写sql的时候,如果我们没有加name这个索引条件字段,那么这个联合索引是不会起效的,不会走索引的,因为单单从后面几个字段我们是没办法判断索引在B+Tree中的排序的。因为我们的联合索引是排好了序的,而且是用第一个字段来一次排序,如果第一个字段不相等,那么就不用以后面的字段来排序,如果不用联合索引中的第一个字段,我们是没办法判断索引在B+Tree中的排序的。
我们的数据库一般都会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增删改查操作,可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。
这些问题的本质都是数据库的多事务并发问题,为了解决多事务并发问题,数据库设计了事务隔离机制、锁机制、MVCC多版本并发控制隔离机制,用一整套机制来解决多事务并发问题。接下来,我们会深入讲解这些机制,让大家彻底理解数据库内部的执行原理。
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题–最后的更新覆盖了由其他事务所做的更新。
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫做“脏读”。
一句话:事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
一句话:事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
一句话:事务A读取到了事务B提交的新增数据,不符合隔离性
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。
同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读"和“幻读”并不敏感,可能更关心数据并发访问的能力。
show variables like 'tx_isolation';
set tx_isolation='REPEATABLE-READ';
-- 读未提交read-uncommitted;
-- 读已提交read-committed;
-- 可重复读repeatable-read;
-- 串行化 serializable
Mysql默认的事务隔离级别是可重复读,用Spring开发程序时,如果不设置隔离级别默认用Mysql设置的隔离级别,如果Spring设置了就用已经设置的隔离级别。
读锁(共享锁,S锁(Shared)):针对同一份数据,多个读操作可以同时进行而不会互相影响,但不允许其他事务修改,可在sql语句后面加lock in share mode
写锁(排它锁,X锁(eXclusive)):当前写操作没有完成前,它会阻断其他写锁和读锁,在sql语句后加for update
每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;一般用在整表数据迁移的场景。
CREATE TABLE `mylock` (
`id` INT (11) NOT NULL AUTO_INCREMENT,
`NAME` VARCHAR (20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = MyISAM DEFAULT CHARSET = utf8;
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('1', 'a');
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('2', 'b');
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('3', 'c');
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('4', 'd')
lock table 表名称 read(write),表名称2 read(write);
show open tables;
unlock tables;
lock table mylock read

当前session和其他session都可以读该表
当前session中插入或者更新锁定的表都会报错,其他session插入或更新则会等待
lock table mylock write

当前session对该表的增删改查都没有问题,其他session对该表的所有操作被阻塞
每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。
一个session开启事务更新不提交,另一个session更新同一条记录会阻塞,更新不同记录不会阻塞
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`balance` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `test`.`account` (`name`, `balance`) VALUES ('lilei', '450');
INSERT INTO `test`.`account` (`name`, `balance`) VALUES ('hanmei', '16000');
INSERT INTO `test`.`account` (`name`, `balance`) VALUES ('lucy', '2400');
1:打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:
set tx_isolation='read-uncommitted';

2:在客户端A的事务提交之前,打开另一个客户端B,更新表account:

3:这时,虽然客户端B的事务还没提交,但是客户端A就可以查询到B已经更新的数据:

4:一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据:

5:在客户端A执行更新语句update account set balance = balance - 50 where id =1,lilei的balance没有变成350,居然是400,是不是很奇怪,数据不一致啊,如果你这么想就太天真 了,在应用程序中,我们会用400-50=350,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别。

set tx_isolation='read-committed';
(1)打开一个客户端A,并设置当前事务模式为read committed(未提交读),查询表account的所有记录:

(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:

(3)这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题:

(4)客户端B的事务提交

(5)客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题

(1)打开一个客户端A,并设置当前事务模式为repeatable read,查询表account的所有记录
set tx_isolation='repeatable-read';

(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交

(3)在客户端A查询表account的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题

(4)在客户端A,接着执行update account set balance = balance - 50 where id = 1,balance没有变成400-50=350,lilei的balance值用的是步骤2中的350来算的,所以是300,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了MVCC(multi-version concurrency control)机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)。

(5)重新打开客户端B,插入一条新数据后提交

(6)在客户端A查询表account的所有记录,没有查出新增数据,所以没有出现幻读;

(7)验证幻读
在客户端A执行update account set balance=888 where id = 4;能更新成功,再次查询能查到客户端B新增的数据。

(1)打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值:
set tx_isolation='serializable';

(2)打开一个客户端B,并设置当前事务模式为serializable,更新相同的id为1的记录会被阻塞等待,更新id为2的记录可以成功,说明在串行模式下innodb的查询也会被加上行锁。
如果客户端A执行的是一个范围查询,那么该范围内的所有行包括每行记录所在的间隙区间范围(就算该行数据还未被插入也会加锁,这种是间隙锁)都会被加锁。此时如果客户端B在该范围内插入数据都会被阻塞,所以就避免了幻读。
这种隔离级别并发性极低,开发中很少会用到。

假设account表里数据如下:

Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一下,但是在整体并发处理能力方面要远远优于MYISAM的表级锁定的。当系统并发量高的时候,Innodb的整体性能和MYISAM相比就会有比较明显的优势了。
但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MYISAM高,甚至可能会更差。
通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况
show status like 'innodb_row_lock%';
对各个状态量的说明如下:
对于这5个状态变量,比较重要的主要是
尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手制定优化计划。
‐‐ 查看事务
select * from INFORMATION_SCHEMA.INNODB_TRX;
‐‐ 查看锁
select * from INFORMATION_SCHEMA.INNODB_LOCKS;
‐‐ 查看锁等待
select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
‐‐ 释放锁,trx_mysql_thread_id可以从INNODB_TRX表里查看到
kill trx_mysql_thread_id
‐‐ 查看锁等待详细信息
show engine innodb status\G;
set tx_isolation=‘repeatable-read’
大多数情况mysql可以自动检测死锁并回滚产生死锁的那个事务,但是有些情况mysql没法自动检测死锁。

undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链。

在可重复读隔离级别,当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view,该视图在事务结束之前都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成),
这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成,事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。
若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若 row 的 trx_id就是当前自己的事务是可见的);
若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。
注意:
begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句,事务才真正启动,才会向mysql申请事务id,mysql内部是严格按照事务的启动顺序来分配事务id的。
MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。

删库是不需要跑路的,因为我们的SQL执行时,会将sql语句的执行逻辑记录在我们的bin-log当中,
binlog是Server层实现的二进制日志,他会记录我们的cud操作。Binlog有以下几个特点:
1、Binlog在MySQL的Server层实现(引擎共用)
2、Binlog为逻辑日志,记录的是一条语句的原始逻辑
3、Binlog不限大小,追加写入,不会覆盖以前的日志
如果,我们误删了数据库,可以使用binlog进行归档!要使用binlog归档,首先我们得记录binlog,因此需要先开启MySQL的binlog功能。
配置开启binlog
log‐bin=/usr/local/mysql/data/binlog/mysql‐bin
注意5.7以及更高版本需要配置本项:server‐id=123454(自定义,保证唯一性);
#binlog格式,有3种statement,row,mixed(建议使用row或者mixd);statement会记录sql的执行逻辑,row会记录影响的执行的结果,mixed两者结合。
binlog‐format=ROW
#表示每1次执行写入就与硬盘同步,会影响性能,为0时表示,事务提交时mysql不做刷盘操作,由系统决定
sync‐binlog=1
mysql> show variables like ‘%log_bin%’; 查看bin‐log是否开启
flush logs; 会多一个最新的bin‐log日志
show master status; 查看最后一个bin‐log日志的相关信息
reset master; 清空所有的bin‐log日志

从bin‐log恢复数据