SQL数据库的整体结构、索引、MVCC、锁、日志、查询优化,三大范式等

关系型数据库和非关系型数据库
SQL:关系型数据库指的是使用关系模型(二维表格模型)来组织数据的数据库。(mysql,sqlserver,sqllite,oracle)
关系数据库的优点:
容易理解,符合正常思维方式;都是用表格形式,格式统一,方便复杂查询
完整性约束和事务机制可以很好防止数据冗余,数据不一致的问题。
可以做一些子句的联系多个表的复杂查询支持;
数据存盘,不会丢失。

非关系型数据库又被称为 NoSQL(Not Only SQL ),意为不仅仅是 SQL。通常指数据以对象的形式存储在数据库中,而对象之间的关系通过每个对象自身的属性来决定,常用于存储非结构化的数据
常见的NOSQL数据库:
键值数据库:Redis、Memcached、Riak
列族数据库:Bigtable**、HBase**、Cassandra
文档数据库:MongoDB、CouchDB、MarkLogic
图形数据库:Neo4j、InfoGrid
随着互联网企业的不断发展,数据日益增多,因此关系型数据库面对海量的数据会存在很多的不足。
比如:
面对海量用户连接,无法满足同时连接,响应速度太慢,无法容纳新的数据类型,扩展能力差(不过也有sql集群,主从复制原理:主节点开启binlog记录所有修改事件,从节点开启IO线程假装客户端请求数据,主节点开启dump线程把数据传到从节点relay日志,从节点执行一遍,但是没有redis集群那么完善)
非关系优点:
存储信息的类型很多,不像关系型只有规定那些。比如redis就可以存地理数据GEO类型;
非常快,比如redis是基于内存的,另外就是没有SQL层的解析,没有事务机制的消耗;

缺点:
学习成本高,没有SQL语言支持,比如redis需要lua语言来写脚本;
没有事务,不是很安全;
功能不够完善,复杂查询 不容易实现;
存在内存的话还需要采用一些方式来存盘,如果不是always,会有数据丢失的风险。

mysql数据库的组成部分?一条查询语句的执行过程?
MySQL 的架构共分为两层:Server 层和存储引擎层,
Server 层负责建立连接、分析和执行 SQL。MySQL 大多数的核心功能模块都在这实现,主要包括连接器,查询缓存、解析器、预处理器、优化器、执行器等。另外,所有的内置函数(如日期、时间、数学和加密函数等)和所有跨存储引擎的功能(如存储过程、触发器、视图等。)都在 Server 层实现。
存储引擎层负责数据的存储和提取。支持 InnoDB、MyISAM、Memory 等多个存储引擎,不同的存储引擎共用一个 Server 层。现在最常用的存储引擎是 InnoDB,从 MySQL 5.5 版本开始, InnoDB 成为了 MySQL 的默认存储引擎。

查询一条语句的过程:
1、连接器连接服务器:mysql -h $ip -u $user -p。连接就是先建立TCP连接再看用户密码是否正确;
2、查询缓存,命中直接返回客户端。对于更新比较频繁的表,查询缓存的命中率很低的,因为只要一个表有更新操作,那么这个表的查询缓存就会被清空。所以,MySQL 8.0 版本直接将查询缓存删掉了。
3、分析器解析sql语句。词法分析分析关键字,语法分析看是否符合语法;
4、预处理+优化+真正执行。预处理包括检查 SQL 查询语句中的表或者字段是否存在;将 select * 中的 * 符号,扩展为表上的所有列。优化器主要负责将 SQL 查询语句的执行方案确定下来,比如在表里面有多个索引的时候,优化器会基于查询成本的考虑,来决定选择使用哪个索引。要想知道优化器选择了哪个索引,我们可以在查询语句最前面加个 explain 命令,这样就会输出这条 SQL 语句的执行计划。

为什么要用索引?索引几种形式区别?聚簇索引非聚簇索引?主键索引辅助索引?B,B+树区别?索引失效情况?索引的底层原理(小林coding)
就像书中的目录,就是充当索引的角色,方便我们快速查找书中的内容,所以索引是以空间换时间的设计思想
那换到数据库中,索引的定义就是帮助存储引擎快速获取数据的一种数据结构,形象的说就是索引是数据的目录。可以大大加快数据的检索速度,这也是创建索引的最主要的原因。(当然也有一些其他功能比如唯一索引保证完整性,加速表的连接等)
索引有很多类别:要看从什么角度划分:
按「数据结构」分类:B+tree索引、Hash索引、Full-text索引,B树索引。
按「物理存储」分类:聚簇索引(主键索引)、二级索引(辅助索引)。
按「字段特性」分类:主键索引、唯一索引、普通索引、前缀索引。

聚簇索引和非聚簇索引
要明确,Mysql的MyISAM和InnoDB,使用的分别是非聚簇索引和聚簇索引。但是二者都是使用B+树作为数据结构的。区别在于,聚集索引的叶子结点数据域存储的是那一行数据本身,非聚集索引叶子结点的数据域存储的是那行数据的地址
1、由于聚簇索引存储的是数据本身,而非聚簇索引只存储指针,因此聚簇索引会占用更多的空间。
如果对非主键稿聚簇索引,那么还需要维护一个辅助索引,如果主键非常长,那么辅助索引将会非常大,很低效。
2、聚簇索引在增删改的时候比非聚簇索引慢很多,因为插入新数据时需要检测主键是否重复,这需要遍历主索引的所有叶节点,而非聚簇索引的叶节点保存的是数据地址,占用空间少,因此分布集中,查询的时候I/O更少,聚簇索引的主索引中存储的是数据本身,数据占用空间大,分布范围更大,可能占用好多的扇区,因此需要更多次I/O才能遍历完成。
综上,聚簇索引适用于主键,查询快,只要一次就行,而非聚簇索引还要根据指针去找;
但是,聚簇索引占用空间大一点,在非主键用聚簇索引还需要维护辅助索引;
最后,聚簇索引对于更新表影响大一点,维护成本更高(因为是数据的移动,I/O更多),非聚簇索引对于更新表影响小一点。

哈希索引:字面意思,就是哈希表的结构,键是列值,值是该行数据的物理地址。但是由于哈希表是单个查询很快,对于范围查询没有办法,而范围查询是很常见的,所以一般数据库不用哈希索引的结构。
全文索引:这也是一个比较独特的索引。它主要用于text的数据类型,比如如果使用普通索引,那么匹配文本前几个字符还是可行的,但是想要匹配文本中间的几个单词,那么就要使用LIKE %word%来匹配,这样需要很长的时间来处理,响应时间会大大增加,这种情况,就可使用时FULLTEXT索引了,在生成FULLTEXT索引时,会为文本生成一份单词的清单,在索引时及根据这个单词的清单来索引。(适用场景有限

组合索引:也就是多个列组成索引。注意组合索引的列不允许有空值。并且符合最左前缀原则,也就是如果组合A,B,C. 那么A,AB,ABC是符合条件的组合,而B,C等就不是。

B树实际上相当于一个平衡搜索多叉树,具体可以自己学习,我只说重点部分。B树每个节点有键、数据、指针三部分。每个节点有2到M个孩子。并且,所有叶子结点都在同一层上,所以说是平衡,这也是为了减少搜索时间(logn)。
B+树作为数据库引擎MyISAM和InnoDB使用的索引结构。它和B树的区别在于以下几点:
1、B+树只有叶子结点带有数据域,其余的只有键和指针。
2、B+树每个节点有M个孩子和M个键。(或者M和M+1,这不重要)
3、B+树在叶子结点添加了指向相邻叶子结点的指针。

为什么B+树比B树更适合作为索引呢?
B+树由于非叶子节点没有数据域,所以能够携带更多的键,所以B+树的层数少,看起来更矮胖一点。那么查询时,B+树所进行的I/O次数更少,因为途中经过每一层,我们都需要进行一次I/O读取一个结点。
由于B+树在叶子结点增加了指向相邻叶子结点的指针,当进行区间查询时,只要沿着指针读取就可以,天然具备排序功能。
最后,谈一下索引什么时候该用,什么时候不该用?
适用场景:
1、经常用于查询或排序的列,比如where, order by中的列需要索引,因为B+索引的查询和排序相对于扫描整张表而言是快的。就像用二叉搜索树找某个数字是log(n)复杂度,而普通数据是O(n)复杂度;
2、主键自动会创建索引;
3、在经常用在连接的列上,这些列主要是外键,可以加快连接速度。
4、用于聚合函数的列可以建立索引,为啥,因为B+树的全文搜索很快,只要在根据叶子结点链表搜下去就可以;

不适用场景:
1、经常增删改的列,因为维护索引结构需要时间和空间代价;
2、不怎么查询的列,因为,索引目的就是为了加速查询,如果不查询,索引就没用,而且维护需要代价;
3、数据量少的列。如果在测试数据库里只有几百条数据记录,它们往往在执行完第一条查询命令之后就被全部加载到内存里,这将使后续的查询命令都执行得非常快–不管有没有使用索引。索引是减少了I/O次数,即在磁盘查询的次数,所以快,在内存中,数据的查询很快,索引意义不大。
4、对于text等很大的数据,不要建立索引,因为聚簇索引中数据太大了,占用空间很大。除非text数据经常用到模糊查询,可以尝试建立全文索引。

总之,索引是在查询性能和修改性能的均衡,因为索引利于查询,不利于修改。

索引优化的方法?
前缀索引优化:使用前缀索引是为了减小索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减小索引项的大小。但是不能让order by。
覆盖索引优化:建立一个包含查询字段的联合索引。从二级索引中查询得到记录,而不需要通过主键索引查询获得,可以避免回表的操作
主键索引递增:每次插入一条新记录,都是追加操作,不需要重新移动数据,因此这种插入数据的方法效率非常高。没有碎片。
防止索引失效。
索引失效场景:为什么这些场景会失效
左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效;因为索引 B+ 树是按照「索引值」有序排列存储的,只能根据前缀进行比较。
对索引列做了计算、函数、类型转换操作,这些情况下都会造成索引失效;因为索引保存的是索引字段的原始值,而不是经过函数计算后的值,自然就没办法走索引了。
联合索引没有正确使用需要遵循最左匹配原则
在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。是因为 OR 的含义就是两个只要满足一个即可,因此只有一个条件列是索引列是没有意义的,只要有条件列不是索引列,就会进行全表扫描。

mysql行数据是怎么存储的?
(MySQL 的 NULL 值会占用空间吗?
MySQL 怎么知道 varchar(n) 实际占用数据的大小?
varchar(n) 中 n 最大取值为多少?
行溢出后,MySQL 是怎么处理的?)

创建一个表后,会在 /var/lib/mysql/ 目录里面创建三个文件:db.opt,用来存储当前数据库的默认字符集和字符校验规则。t_order.frm ,t_order 的表结构会保存在这个文件;t_order.ibd,t_order 的表数据会保存在这个文件。
表空间由段(segment)、区(extent)、页(page)、行(row)组成。,InnoDB 的数据是按「页」为单位来读写的,也就是说,当需要读一条记录的时候,并不是将这个行记录从磁盘读出来,而是以页为单位,将其整体读入内存。默认每个页的大小为 16KB。

行格式:分别是 Redundant、Compact、Dynamic和 Compressed 行格式。现在用Dynamic,下面介绍Compact,因为后面都是根据compact改进的。

SQL数据库的整体结构、索引、MVCC、锁、日志、查询优化,三大范式等_第1张图片
记录的额外信息包含 3 个部分:变长字段长度列表、NULL 值列表、记录头信息。
varchar(n) 和 char(n) 的区别是什么,相信大家都非常清楚,char 是定长的,varchar 是变长的,变长字段实际存储的数据的长度(大小)不固定的。所以,在存储数据的时候,也要把数据占用的大小存起来,存到「变长字段长度列表」里面。
要注意的是存放是逆序的,这样可以使得位置靠前的记录的真实数据和数据对应的字段长度信息可以同时在一个 CPU Cache Line 中,这样就可以提高 CPU Cache 的命中率。同样的道理, NULL 值列表的信息也需要逆序存放。
因此,如果可以确定字符长度的,就用char,避免长度列表占用空间。
表中的某些列可能会存储 NULL 值,如果把这些 NULL 值都放到记录的真实数据中会比较浪费空间,所以 Compact 行格式把这些值为 NULL 的列存储到 NULL值列表中。如果存在允许 NULL 值的列,则每个列对应一个二进制位(bit),二进制位按照列的顺序逆序排列。
当数据表的字段都定义成 NOT NULL 的时候,这时候表里的行格式就不会有 NULL 值列表了。
所以在设计数据库表的时候,通常都是建议将字段设置为 NOT NULL,这样可以至少节省 1 字节的空间(NULL 值列表至少占用 1 字节空间)。

记录头信息中包含的内容很多:
delete_mask:标识这条数据是否删除,所以删除一条数据不是马上删除的;
next_record:下一条记录的位置。从这里可以知道,记录与记录之间是通过链表组织的。在前面我也提到了,指向的是下一条记录的「记录头信息」和「真实数据」之间的位置,这样的好处是向左读就是记录头信息,向右读就是真实数据,比较方便。
record_type:表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录

记录真实数据部分除了我们定义的字段,还有三个隐藏字段,分别为:row_id、trx_id、roll_pointer,我们来看下这三个字段是什么。
SQL数据库的整体结构、索引、MVCC、锁、日志、查询优化,三大范式等_第2张图片

row_id
如果我们建表的时候指定了主键或者唯一约束列,那么就没有 row_id 隐藏字段了。如果既没有指定主键,又没有唯一约束,那么 InnoDB 就会为记录添加 row_id 隐藏字段。row_id不是必需的,占用 6 个字节。
trx_id
事务id,表示这个数据是由哪个事务生成的。 trx_id是必需的,占用 6 个字节。
roll_pointer
这条记录上一个版本的指针。roll_pointer 是必需的,占用 7 个字节。实现 MVCC 机制(具体怎么实现?
varchar(n) 中 n 最大取值为多少?
MySQL 规定除了 TEXT、BLOBs 这种大对象类型之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过 65535 个字节。
如果ascii字符集,一个字符占一个字节。在数据库表只有一个 varchar(n) 字段且字符集是 ascii 的情况下,varchar(n) 中 n 最大值 = 65535 - 2 - 1 = 65532。

行溢出后,MySQL 是怎么处理的?
MySQL 中磁盘和内存交互的基本单位是页,一个页的大小一般是 16KB,也就是 16384字节,而一个 varchar(n) 类型的列最多可以存储 65532字节,一些大对象如 TEXT、BLOB 可能存储更多的数据,这时一个页可能就存不了一条记录。这个时候就会发生行溢出,多的数据就会存到另外的**「溢出页」**中。真实数据处用 20 字节存储指向溢出页的地址,从而可以找到剩余数据所在的页。Compressed 和 Dynamic 这两种格式采用完全的行溢出方式,记录的真实数据处不会存储该列的一部分数据,只存储 20 个字节的指针来指向溢出页。而实际的数据都存储在溢出页中。

事务机制,MVCC机制怎么实现?四种隔离级别?
ACID特性:原子性(一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节)undo log回滚日志实现
一致性(事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态)其他三个满足,这个自然满足
隔离性(多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的)MVCC机制实现
持久性(事务完成后落盘) redo log实现
这次将重点介绍事务的隔离性,这也是面试时最常问的知识的点。

四种隔离级别以及会出现的问题
读未提交(脏读)读已提交(不可重复读),可重复读(幻读MySQL InnoDB 引擎的默认隔离级别),串行
脏读就是读了其他事务修改的未提交的数据,因为可能回滚,所以这个数据是过期的;
不可重复读就是:A事务前后两次读取数据,中间可能有其他事务读取并修改数据,这样A前后读取的数据不一致了;
幻读:在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况。

MySQL 在「可重复读」隔离级别下,可以很大程度上避免幻读现象的发生(注意是很大程度避免,并不是彻底避免),所以 MySQL 并不会使用「串行化」隔离级别来避免幻读现象的发生,因为使用「串行化」隔离级别会影响性能
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。(虽然MVCC就算中间B插入一条数据,A查不到,但是如果A更新这个记录,这个记录的trx_id 就是A的,就能看到了,因此会出现幻读)
针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。(但是如果事务开启后,没有立即用当前读,可能前面select就插入了)
所以最好的办法就是开启事务后立即用select … for update,防止其他事务插入数据。
所以select … for update必须用在事务中,是为了在高并发场景下,对于金钱库存等要求准确性的记录,防止事务中间被其他事务烦扰比如插入数据更新数据。它会加排它锁,阻塞其他线程。

这四种隔离级别具体是如何实现的呢?
对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。

那 Read View 到底是个什么东西?
Read View 有四个重要的字段:
m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务。
min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
creator_trx_id :指的是创建该 Read View 的事务的事务 id。
SQL数据库的整体结构、索引、MVCC、锁、日志、查询优化,三大范式等_第3张图片

那么,记录是否可见取决于:
如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值,说明是在read view前提交的,记录可见;
如果大于max_trx_id说明创建read view时事务还没有启动,不可见;
如果在他们之间,如果rx_id 在 m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。如果记录的 trx_id 不在 m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。

我们还需要了解聚簇索引记录中的两个隐藏列。
SQL数据库的整体结构、索引、MVCC、锁、日志、查询优化,三大范式等_第4张图片
trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里;
oll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。

**可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。**比如A的活跃列表是51,B是51,52。然后A修改了数据,此时数据记录的trx_id是51,undo日志里连着50原来的数据,就算A提交修改,因为一直用的是启动时的read view,所以认为51是活跃列表中的,所以不能读,顺着undo找到50读。

**读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。**所以当另一事务提交,活跃事务列表就不包含51了。

数据页角度看索引
每一个索引节点就是一个数据页,非叶子节点只存储了页目录,也就是包含的键的范围,在叶子结点找到目标页后,又会在该页内进行二分法快速定位记录所在的分组(槽号),最后在分组内进行遍历查找。

mysql中的锁机制
按照锁的功能主要是写锁和读锁
按照锁机制分为乐观锁和悲观锁。乐观锁: 对于出现更新丢失的可能性比较乐观,先认为不会出现更新丢失,在最后更新数据时进行比较。用在并发量不大的场景,性能较高。可以用CAS实现(会有ABA问题),版本号机制实现。
悲观锁就是互斥锁独占锁,;可通过select…for update实现。

在 MySQL 里,根据加锁的范围,可以分为全局锁、表级锁和行锁三类。
全局锁主要用于全库的逻辑备份,一个操作可能涉及几个表。flush tables with read lock
释放全局锁 unlock tables
但是全局锁性能太差,InnoDB 存储引擎默认的事务隔离级别正是可重复读,因此可以采用这种方式来备份数据库。但是,对于 MyISAM 这种不支持事务的引擎,在备份数据库时就要使用全局锁的方法。备份数据库的工具是 mysqldump,在使用 mysqldump 时加上 –single-transaction 参数的时候,就会在备份数据库之前先开启事务

//表级别的共享锁,也就是读锁;
lock tables t_student read;
//表级别的独占锁,也就是写锁;
lock tables t_stuent write;
不过尽量避免在使用 InnoDB 引擎的表使用表锁,因为表锁的颗粒度太大,会影响并发性能,InnoDB 牛逼的地方在于实现了颗粒度更细的行级锁

元数据锁(MDL)
我们不需要显示的使用 MDL,因为当我们对数据库表进行操作时,会自动给这个表加上 MDL,MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。
对一张表进行 CRUD 操作时,加的是 MDL 读锁;
对一张表做结构变更操作的时候,加的是 MDL 写锁
当有线程在执行 select 语句( 加 MDL 读锁)的期间,如果有其他线程要更改该表的结构( 申请 MDL 写锁),那么将会被阻塞,直到执行完 select 语句( 释放 MDL 读锁)。
MDL 是在事务提交后才会释放,这意味着事务执行期间,MDL 是一直持有的。所以长事务时看看是否有事务已经对表加上了 MDL 读锁,如果可以考虑 kill 掉这个长事务,然后再做表结构的变更。

意向锁
意向锁的目的是为了快速判断表里是否有记录被加锁。在使用 InnoDB 引擎的表里对某些纪录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」。如果没有「意向锁」,那么加「独占表锁」时,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢。那么有了「意向锁」,由于在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加「独占表锁」时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,这样就不用去遍历表里的记录。
AUTO-INC 锁
用于主键字段自增,执行完插入语句后就会立即释放。。在 MySQL 5.1.22 版本开始,InnoDB 存储引擎提供了一种轻量级的锁来实现自增。一样也是在插入数据的时候,会为被 AUTO_INCREMENT 修饰的字段加上轻量级锁,然后给该字段赋值一个自增的值,就把这个轻量级锁释放了,而不需要等待整个插入语句执行完后才释放锁。

InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。
普通的 select 语句是不会对记录加锁的,因为它属于快照读。如果要在查询时对记录加行锁,可以使用下面这两个方式,这种查询会加锁的语句称为锁定读。
//对读取的记录加共享锁
select … lock in share mode;
//对读取的记录加独占锁
select … for update;
上面这两条语句必须在一个事务中,因为当事务提交了,锁就会被释放
行级锁的类型主要有三类:
Record Lock,记录锁,也就是仅仅把一条记录锁上;
Gap Lock,间隙锁,锁定一个范围,但是不包含记录本身;
Next-Key Lock:Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身

Record Lock 称为记录锁,锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的。也就是最常用的那两个锁了。

Gap Lock 称为间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象假设,表中有一个范围 id 为(3,5)间隙锁,那么其他事务就无法插入 id = 4 这条记录了,这样就有效的防止幻读现象的发生。
Next-Key Lock 假设,表中有一个范围 id 为(3,5] 的 next-key lock,那么其他事务即不能插入 id = 4 记录,也不能修改 id = 5 这条记录。

插入意向锁
一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁(next-key lock 也包含间隙锁)。防止不同事务在间隙的不同位置插入也互相等待。
加锁规则
select是不会加锁的通过MVCC机制实现,update,delete等更新操作会加锁,select…for update会加锁
两个原则
加锁的基本单位是next-key lock
查找过程中访问到的对象才会加锁
两个优化
索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁
索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁
在线上在执行 update、delete、select … for update 等具有加锁性质的语句,一定要检查语句是否走了索引,如果是全表扫描的话,会对每一个索引加 next-key 锁,相当于把整个表锁住了,这是挺严重的问题。所以这也是为什么要索引?索引不仅会通过B+树结构加快查询速度。而且对于加锁的操作来说,通过索引只会锁住一行,其他线程可以修改表,全表扫描的话会锁住整个表。

mysql的死锁
务A先获取到id=1的行锁,然后事务B获取到id=2的行锁;
接着事务A要获取id=2的行锁,发现被事务B持有,阻塞;
事务B要获取id=1的行锁,发现被事务A持有,阻塞;
两个事务进入死锁状态。
当出现死锁后,有两种处理策略:死锁解除(互斥、占有且等待、不可强占用、循环等待
直接进入等待,直到连接超时,超时时间可通过innodb_lock_wait_timeout设置。
发起死锁检测,发现死锁后主动回滚死锁中的一个事务,让其他事务正常执行。将参数innodb_deadlock_detect设置为on,表示开启死锁检测。

死锁产生的主要原因是因为加锁顺序不一致
SQL数据库的整体结构、索引、MVCC、锁、日志、查询优化,三大范式等_第5张图片

比如事务A插入一条记录成功(update是会自动提交的),然后事务B也插入一条记录成功。接着A又想插入一条记录,B也准备插入一条记录。 这个时候,如果A想插入的插入意向锁正好处于B的间隙锁范围内,那么就会等待;B同理。就形成了循环等待
不过因为INNODB底层超时会强制一个事务让出锁,所以看到某一个执行成功,另一个死锁插入失败了。

如何排查
首先利用show engine INNODB status查看现在不同事务的锁状态。比如A持有什么锁,在等待什么锁(是哪条语句),B同理,然后看看是不是存在循环等待,可以利用锁兼容矩阵来分析。超时的话,可以自己主动回滚事务,也可以利用底层自动超时回滚的机制。

mysql中的buffer bool

mysql中undo log ,redo log ,binlog作用
因此,undo log 两大作用:
实现事务回滚,保障事务的原子性。事务处理过程中,如果出现了错误或者用户执 行了 ROLLBACK 语句,MySQL 可以利用 undo log 中的历史数据将数据恢复到事务开始之前的状态。
实现 MVCC(多版本并发控制)关键因素之一。MVCC 是通过 ReadView + undo log 实现的。undo log 为每条记录保存多份历史数据,MySQL 在执行快照读(普通 select 语句)的时候,会根据事务的 Read View 里的信息,顺着 undo log 的版本链找到满足其可见性的记录。
redo log
实现
事务的持久性
,让 MySQL 有 crash-safe 的能力,能够保证 MySQL 在任何时间段突然崩溃,重启后之前已提交的记录都不会丢失;
写操作从「随机写」变成了「顺序写」,提升 MySQL 写入磁盘的性能。
解释一下:
我们知道mysql在内存里有缓存池的,开启时会申请一段空间给缓存池,所以开始虚拟空间用的很多但是物理空间很小。当有数据更新,会先更新缓存池,然后找个时间落盘,但是内存不稳定,崩溃之后更新丢失了。所以有了redo log.
WAL技术,就是MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。
InnoDB 引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log 的形式记录下来,这个时候更新就算完成了。redo log 是物理日志,记录了某个数据页做了什么修改,比如对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新,在事务提交时,只要先将 redo log 持久化到磁盘即可。写入 redo log 的方式使用了追加操作, 所以磁盘操作是顺序写,而写入数据需要先找到写入位置,然后才写到磁盘,所以磁盘操作是随机写。磁盘的「顺序写 」比「随机写」 高效的多,因此 redo log 写入磁盘的开销更小。
(注意一点,不是每次提交事务都会将 redo log 持久化到磁盘,效率太低了,redo log也是有buffer的,会有合适的时机,所以完全可靠和高速度是不可兼得的)
redo log文件写满了怎么办:redo log是环形的,当buffer pool脏页落盘后,redo log那一部分就没用了,所以InnoDB 用 write pos 表示 redo log 当前记录写到的位置,用 checkpoint 表示当前要擦除的位置。

为什么需要 binlog ?
前面介绍的 undo log 和 redo log 这两个日志都是 Innodb 存储引擎生成的。
MySQL 在完成一条更新操作后,Server 层还会生成一条 binlog,等之后事务提交的时候,会将该事物执行过程中产生的所有 binlog 统一写 入 binlog 文件。
binlog 文件是记录了所有数据库表结构变更和表数据修改的日志,不会记录查询类的操作,比如 SELECT 和 SHOW 操作。
最开始 MySQL 里并没有 InnoDB 引擎,MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。
而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用 redo log 来实现 crash-safe 能力。
binlog主要用于主从复制的,redo log因为边写边擦除没有全量日志,不能用来做备份。主从复制原理:首先主库开启binlog,所有更新操作会写到binlog;从库会创建一个专门的 I/O 线程,连接主库的 log dump 线程,来接收主库的 binlog 日志,再把 binlog 信息写入 relay log 的中继日志里,再返回给主库“复制成功”的响应;从库会创建一个用于回放 binlog 的线程,去读 relay log 中继日志,然后回放 binlog 更新存储引擎中的数据,最终实现主从的数据一致性。

8、如何优化mysql语句
1.设置好字段的属性。比如字段尽可能小,这样查询也会快一点;比如邮政编码,用char(200)明显浪费,用varchar也不好,因为存储时会有一个变长字段列表标记变长字段的长度。比如省份等用ENUM,ENUM当成数值处理,比文本快;比如尽量设置为NOT NULL,这样不用比较NULL值,行记录有一个记录NULL值的字段。

2、尽量避免全表扫描。用explain+SQL的type字段可以查询是不是全表扫描了。办法是在where order的列建立索引。还要看看索引有没有失效。索引失效情况(like %AB,违背了联合索引的最左匹配原则,对字段进行了函数操作,字段用表达式,字符串作为索引和数字比较时会隐式转换成数字,where的or前面是索引,后面不是索引)

  1. 通过索引优化。索引优化的方法有:前缀索引优化(把字符串前几个字符作为索引,减小索引长度,速度快)、覆盖索引优化(主要针对联合索引,把查询的字段作为索引,这样在二级索引就可以查到记录,不需要回表再到主键的聚簇索引上查询),主键索引最好自增(每次插入新数据就会在后面追加,不需要调整树的结构,造成页分裂内存碎片)。
  2. 尽量避免select *,而最好用select 字段名。 插入数据过多考虑批量插入。在join表的时候最好建立索引,且字段最好相同类型。where条件不用in,not in会全表扫描,如果是连续数组最好用between

11、数据库三大范式
第一范式:强调的是列的原子性,也就是数据项不可分。同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。
第一范式问题:数据冗余,列太多;插入(比如插入一个没有学生的新专业,因为主键学号为空)、删除(删除一个专业的所有学生, 将会导致专业被删除)、修改异常(修改A的专业,需要修改好多条记录)
第二范式:在满足第一范式的基础上,实体的每个非主键属性完全依赖于主键属性(消除部分依赖)。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)
第二范式主要解决的是数据冗余的问题
第三范式在2NF的基础上,消除了非主属性对于码的传递函数依赖
BCNF:消除主属性对于码的部分函数依赖与传递函数依赖。

**SQL数据库的整体结构、索引、MVCC、锁、日志、查询优化,三大范式等_第6张图片
**
count(1)、 count(*)、 count(主键字段)在执行的时候,如果表里存在二级索引,优化器就会选择二级索引进行扫描。

所以,如果要执行 count(1)、 count(*)、 count(主键字段) 时,尽量在数据表上建立二级索引,这样优化器会自动采用 key_len 最小的二级索引进行扫描,相比于扫描主键索引效率会高一些。

再来,就是不要使用 count(字段) 来统计记录个数,因为它的效率是最差的,会采用全表扫描的方式来统计。如果你非要统计表中该字段不为 NULL 的记录个数,建议给这个字段建立一个二级索引

count(*) 其实等于 count(0),也就是说,当你使用 count(*) 时,MySQL 会将 * 参数转化为参数 0 来处理.,count(1) 相比 count(主键字段) 少一个步骤,就是不需要读取记录中的字段值(包含空值),所以通常会说 count(1) 执行效率会比 count(主键字段) 高一点。

SQL服务器性能分析的命令有哪些?
explain:可以显示有没有走索引,大概的数量;
show global status like com%; 查询select,update等命令的使用频率为优化提供依据;
slow-query-log: 慢日志查询;
show profiles for query1:查看每个SQL的执行过程(锁,发送时间,排序时间,优化器时间,处理时间等等)和占用资源信息(CPU,内存,io,上下文,时间)

redis服务器性能分析的命令有哪些?
redis -benchmark(测试基准性能,指定数据包,指定命令等)

slowlog get 10:慢日志查询

redis -cli --latency -h -p 检测网络延迟

需要关注的指标:比如info status 看看QPS曲线,如果突然下降说明有慢日志,如果一直比较低可能负载就低。
还有内存指标info memory 比如看看交换分区是不是用的多,多说明经常有换入换出,内存不够,需要扩大内存或者集群;(也可以看命中率)

总结来说,先看自己代码有没有问题,基准测试,再看网络,再看内存问题,这些硬件问题都排除后,再看慢日志,看看AOF,RDB

你可能感兴趣的:(sql数据库学习,数据库,sql)