菜鸟的mysql进阶

菜鸟的mysql进阶

  • 说明
  • 一、mysql的逻辑分层及存储引擎
    • (1)逻辑分层
    • (2)存储引擎
  • 二、事务的ACID原则
  • 三、数据库设计的三大范式
  • 四、索引
    • (1)二叉树搜索树
    • (2)红黑树
    • (3)B树
    • (4)B+树
    • (5)索引
  • 五、SQL解析
  • 六、锁机制
    • (1)行锁
    • (2)表锁
    • (3)范围锁
    • (4)悲观锁
    • (5)乐观锁
    • (6)读写锁
  • 七、SQL优化
    • (1)sql语句优化

说明

更新时间:2020/7/3 17:32,更新了锁机制和SQL语句优化
更新时间:2020/7/2 17:29,更新了索引
更新时间:2020/7/1 22:35,更新了SQL解析及优化,未完待续…

一直想学习一下mysql进阶的相关知识,刚好趁着前段时间redis学完,可以学习一下mysql的进阶,简单了解了一下进阶的相关知识,还挺多的,感觉之前的mysql学的好像只是简单入门而已。本文会持续更新,不断地扩充

本文仅为记录学习轨迹,如有侵权,联系删除

一、mysql的逻辑分层及存储引擎

(1)逻辑分层

一次sql的查询主要包括两个端的操作,客户端client和服务器端server,很多情况下,我们输入增删改查对应的查询语句,即可从服务器端操作数据,像insert、delete、update、select等常见的语句,对应到服务器端其实只是一个api,只是一个接口,从下图可以看到服务器端主要有连接层、服务层、引擎层和存储层,并且有相对应的功能。菜鸟的mysql进阶_第1张图片
其中引擎层就是下面要重点讲的存储引擎。

(2)存储引擎

MySQL中的数据用各种不同的技术存储在文件(或者内存)中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术,你能够获得额外的速度或者功能,从而改善你的应用的整体功能。

查看mysql支持的存储引擎
命令show engines;
菜鸟的mysql进阶_第2张图片
我这边的mysql版本是8.0以上的,可以看到mysql支持的存储引擎还不少,它默认的存储引擎是InnoDB,当然,这个跟我的mysql的配置文件有关
菜鸟的mysql进阶_第3张图片
我这边设置了默认的引擎和字符串,所以不同配置的mysql可能跟我这个有些不同。

MyISAM与InnoDB区别
这里重点提出来学习一下这个两个引擎,因为这两个好像是最常见的两个。

MyISAM InnoDB
文件构成 每个MyISAM在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm文件存储表定义。数据文件的扩展名为.MYD (MYData)。 索引文件的扩展名是.MYI (MYIndex)。 基于磁盘的资源是InnoDB表空间数据文件和它的日志文件,InnoDB 表的大小只受限于操作系统文件的大小,一般为 2GB
事务 不提供事务支持 InnoDB提供事务支持事务
执行速度 MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快 InnoDB相较速度慢了一点
CURD操作 如果执行大量的SELECT,MyISAM是更好的选择 如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表
外键 不支持外键 支持外键
主键 允许没有主键的表存在。 如果没有设定主键,就会自动生成一个 6 字节的主键(用户不可见)。
MyISAM 只支持表级锁 InnoDB 支持行级锁和表级锁,默认是行级锁,行锁大幅度提高了多用户并发操作的性能。
全文索引 MyISAM 支持全文索引。 InnoDB 不支持全文索引 ,但innodb 从 mysql5.6 版本开始提供对全文索引的支持。

经过本人查询之后,发现它们之间的区别还不止这些,这些只是相对比较重要的区别。

MyISAM与InnoDB选择
关于这两种存储引擎的选择,网上有很多说法,我个人比较认可的是要根据需求和存储引擎来决定。比如像一些简单的需求,没有复杂的数据表的关系,数据大多以查询和插入为主,并且对安全性没有特别高的要求,这个时候就建议用MyISAM引擎,如果是涉及到的表有很多,而且各种表还有一些复杂的关联,平时除了查询之外,更新数据也挺频繁的,更重要的是安全性要求特别高,这个时候出于各种综合考虑,建议用InnoDB引擎,因为该引擎支持事务等高级操作。

总之,对于存储引擎的选择,需要结合各种引擎的特点以及需求来进行选择。

二、事务的ACID原则

这里给大家推荐篇两篇博客,这一部分的内容都是基于这两篇博客的整合
博客一和博客二

什么是ACID

ACID 说明
原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency) 事务前后数据的完整性必须保持一致。
隔离性(Isolation) 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

有一个非常经典的银行转账的例子就可以很好的体现了这4个原则。银行转账主要分两个步骤,假设有A和B账户,A账户现有800元,B账户现有200元
菜鸟的mysql进阶_第4张图片
转账过程:A减200元,B加200元两个步骤要么都成功,要么都失败,不会出现A减200后,B却不加200的情况,这就是原子性,转账前A加B总额1000元,转账后也是1000元,这就是一致性,如果有多个用户并发的在执行转账,那么它们之前的事务应该是互补干扰的,这就是隔离性,事务执行成功后,即转账成功后,数据就被持久化到数据库了,这就是持久性

脏读、不可重复读和幻读
在进行web系统的开发的时候,有一个必须要考虑的重点,那就是并发处理,虽然自己平时学校里面的一些实训很少会做并发的处理,但是学到现在,自己会有意识的考虑并发的一些情况。额,扯远了,就是说在高并发的情况,可能会出现脏读、不可重复读和幻读的情况,下面给出相应的概述。

  • 脏读
    所谓脏读,就是指事务A读到了事务B还没有提交的数据,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务–>取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。

  • 不可重复读
    所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。还是以银行取钱为例,事务A开启事务–>查出银行卡余额为1000元,此时切换到事务B事务B开启事务–>事务B取走100元–>提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。

  • 幻读
    所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。比如学生信息,事务A开启事务–>修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务–>事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。

事务隔离级别
其实在应对上面的并发问题,我第一个想到的是用锁。。。,为了应对上面的出现的并发问题,就有了事务隔离级别,因为事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡。
事务隔离级别有4种,但是像Spring会提供给用户5种

事务隔离级别 说明
DEFAULT 默认隔离级别,每种数据库支持的事务隔离级别不一样,如果Spring配置事务时将isolation设置为这个值的话,那么将使用底层数据库的默认事务隔离级别。顺便说一句,如果使用的MySQL,可以使用"select @@tx_isolation"来查看默认的事务隔离级别
READ_UNCOMMITTED 读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用
READ_COMMITED 读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读
REPEATABLE_READ 重复读取,即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
SERLALIZABLE 串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了

给出网上整理的图
菜鸟的mysql进阶_第5张图片

级别越高效率越低,一般情况下会设置为READ_COMMITED,此时避免了脏读,并发性也还不错,之后再通过一些别的手段去解决不可重复读和幻读的问题就好了。

三、数据库设计的三大范式

关于数据库设计的三大范式,首推这篇博客

第一范式(1NF)
要求数据库表的每一列都是不可分割的原子数据项。
例子:
在这里插入图片描述
像这种情况是不行的,因为家庭信息和学校信息的数据是可以再分的,比如家庭地址,家里人口数等,是属于可分割的原子数据项,所以上面这张表是不符合第一范式的,最好的做法是再拆分出家庭信息表和学校信息表

第二范式(2NF)
在满足第一范式的前提下,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖),第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。

一句话总结就是,一张表只能存放对应的一件事情
例子
在这里插入图片描述
很明显,这种设计是不符合第二范式的,单从字段上看就知道,这张表描述了两件事情,产品和订单,所以,这种设计违反了第二范式。

第三范式(3NF)
在满足第一和第二范式的前提下,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖),第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

一句话总结就是,表的每一个字段都应该直接与主键相关,间接相关是不行的
例子
在这里插入图片描述
很明显,后面班主任相关的字段好像跟主键学号不是直接相关的,而像是间接相关的,所以这也是不符合第三范式的,应该通过外键,将班主任与学生关联起来,两张表相关联。

规范与性能
既然三大范式是规范的话,那数据库设计是不是都要符合三大范式才行呢,显然不是的,说到规范就必须要说到性能了,很多情况下,规范和项目的性能往往不能兼得,为了规范化,得牺牲一部分性能,尤其是在海量数据的情况下,所以,在规范和性能之间要做一个权衡。

不过就个人而已,我会优先考虑性能,然后再考虑规范化,我宁愿查询的性能高一点,哪怕是不符合三大范式,最常见的就是给表增加一些冗余字段,将多表查询变为单表查询,虽然可能不符合三大范式,但在大量数据的情况下,这能提高效率。

四、索引

索引可以简单理解为一种数据结构,这种结构可以帮我们实现数据库的快速查找,所以,一般加了索引,可以极大的提高数据库的查找效率,在开始学习所以的前,有必要先学习一下下面的几种数据据结构。

在学习数据结构的时候,我们老师重点讲过树,作为一种数据结构,树是重中之重,只可惜当时学的时候没有重视它,现在才知道原来树结构的一些应用,像mysql数据库的索引,底层用的就是B+树,用这种结构可以实现快速查找,这个后面会详细展开,先简单的复习一下二叉树。

二叉树又可以分为一般二叉树、完全二叉树、满二叉树、线索二叉树、霍夫曼树、二叉排序树、平衡二叉树、红黑树、B树,二叉搜索树等等。

(1)二叉树搜索树

定义

1)每个节点有一个唯一的key值,且所有结点互不相同;
(2)左子树所有key值小于根的key值;
(3)右子树所有key值大于根的key值;
(4)左右子树都是二叉搜索树。

利用这种结构可以实现快速的查找,下面以mysql没加索引时的查找效率和二叉搜索树的查找效率比较。

案例
假设下面这张表名为:test
菜鸟的mysql进阶_第6张图片
查找:select * from test where Col2 = 23

正常情况下如果执行上面这条语句的话,它会从头开始搜索,一共需要执行7次才可以定位到23这行数据,但是如果用二叉搜索树的话,搜索过程是先查询34->22->23,只需3次就可以定位到23这行数据,查询效率提高了不少。

这种二叉树搜索树的结构似乎很完美,然而,它也有它的短板,假设如果将上面的图中的Col1用二叉树搜索树存储的话,查找23需要多少次呢?
菜鸟的mysql进阶_第7张图片
结果是7次,这种情况下,查询的效率就很低了。

(2)红黑树

红黑树是一个平衡的二叉树,也叫二叉平衡树,但不是一个完美的平衡二叉树。红黑树是在二叉搜索树的基础上,引入了红和黑两种颜色,并且结合红黑树的5条性质,来保证树的平衡。

性质

(1)每个结点要么是红的要么是黑的。  
(2)根结点是黑的。  
(3)每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。  
(4)如果一个结点是红的,那么它的两个儿子都是黑的。  
(5)任意结点到叶子结点的每条路径都包含相同数目的黑结点。

什么,这些性质有点难理解,ok,下面逐条介绍每一条性质

菜鸟的mysql进阶_第8张图片
注意:性质 3 中指定红黑树的每个叶子节点都是空节点,而且并叶子节点都是黑色。但 Java 实现的红黑树将使用 null 来代表空节点,因此遍历红黑树时将看不到黑色的叶子节点,反而看到每个叶子节点都是红色的。

菜鸟的mysql进阶_第9张图片
像这个图,上面为NIL的均为叶子节点,上图也是符合5条性质的。

红黑树的左旋右旋
红黑树主要通过性质5进行左旋右旋,从而保证平衡,具体怎么操作看下图
菜鸟的mysql进阶_第10张图片
可惜不会发动图,用动图的话可能更加直观。这样就解决了上面的二叉搜索树短板问题。

(3)B树

关于B树这里推荐一篇文章

B树又叫为B-树,这两个是同一种树,只是叫法不同而已。B树和平衡二叉树稍有不同的是B树属于多叉树又名平衡多路查找树(查找路径不只两个),B树的查询有点像二分法查询。

特征

一个m阶的B树具有如下几个特征:
1、每个节点最多有m-1个关键字(可以存有的键值对)。

2、根节点最少可以只有1个关键字。

3、非根节点至少有m/2个关键字。

4、每个节点中的关键字都按照从小到大的顺序排列

5、所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。

6、每个节点都存有索引和数据,也就是对应的key和value。

7、所有的索引元素不重复

插入

遵循规则:
这里以一个4阶的B树为例,插入数据1234561)节点拆分规则:当前是要组成一个4阶B树,那么此时m=4,关键字数必须小于等于4-1=3,所以当一个节点的值超过3就要进行拆分

(2)拆分规则:节点中中间的值往上个节点提,左右两边独立出来两个节点

(3)排序规则:满足节点本身比左边节点大,比右边节点小的排序规则;

菜鸟的mysql进阶_第11张图片

只要满足拆分规则就会进行拆分

查询
查询就更简单了,以下面构建的B树为例查询数值3
菜鸟的mysql进阶_第12张图片
先查根节点,3小于11,所以往左边走,直接定位到3,效率及其高

又比如下面的B树,查询数值3

菜鸟的mysql进阶_第13张图片
先查根节点,3小于9,往左边走,3位于2和6之间,往中间走,定位到3和5,从而定位到数值3。

注意:这里有一个重点,那就是每一个节点都是用key-value的方式存在的,key对应值,value对应key所在的地址,正是这样的结构才能有高效率的查询

(4)B+树

B+树可以看成是B树的一个变种,他们之前存在很多相同点,当然也有不同点,而且相对于B树,B+树查询的效率更高。斜体样式

菜鸟的mysql进阶_第14张图片
插入
B+树的插入跟B树的插入相类似,只是这里会有一个索引节点的存在

插入的规则:当节点元素数量大于m-1的时候,按中间元素分裂成左右两部分,中间元素分裂到父节点当做索引存储,但是,本身中间元素还是分裂右边这一部分的。

这里以一个5阶的B+树为例
菜鸟的mysql进阶_第15张图片

查询
以下面的例子,查询3,首先查询根节点
菜鸟的mysql进阶_第16张图片
然后3小于8,查询左边节点
菜鸟的mysql进阶_第17张图片
3位于2和5之间,所以查询2和5之间的节点
菜鸟的mysql进阶_第18张图片
注意:查询的方式好像跟B树差不多,但是细分的话还是有不同的
(1)B+树中间的索引节点不存放数据,只存放索引,所以占的空间相对B树少,因此同样大小的磁盘页,B+树可以存放更多数据,所以在数据量相同的情况下,B+树相对于B树显得更加“矮胖”,查询的I/O次数也较少,查询效率也比较高。

(2)范围查找,B树需要用中值遍历等操作,而B+树只需要遍历叶子节点做链表的遍历即可,这一点上,B+树的效率就比B树要高得多

总结

1)B树和B+树根节点至少一个元素,非根节点元素范围:m/2 <= k <= m-12)B树的所有节点都存放有数据,而B+树有两种类型的节点:索引结点和叶子结点。索引结点只存储索引,数据都存储在叶子节点。

(3)单一节点存储的元素更多,使得查询的IO次数更少,所以也就使得它更适合做为数据库MySQL的底层数据结构了

(4)B+树叶子节点之间有指针指向下一个叶子节点

(5)索引

索引的分类与创建
主要有主键索引,唯一索引,单值索引和复合索引
菜鸟的mysql进阶_第19张图片
底层原理
索引为什么那么快呢?这就需要探究一下它的底层原理,目前mysql的索引底层都是基于B+树的数据结构存储的,但又不是完全是B+树,是经过改造的B+树,B树和B+树就像上面介绍的那样,在查询的时候效率是及其高效的,除此之外底层原理还有Hash结构,也是高效查询,但Hash不支持范围查询。

优势与劣势
关于mysql的索引的概念这里就不再叙述,这里简单介绍一下索引的优势和劣势

优势
(1)高效的查询,增加了索引的字段,查询效率及其高,足以支撑千万级别的数据量,即使是千万级别的数据量查询效率依旧很快
(2)因为mysql的索引底层用的是B+树,好像之前是B树来着,这种树结构本身就是排好序的,所以需要排序的时候可以直接拿过来用

劣势
(1)需要额外的空间来存储索引,索引本身就是一种数据结构,所以需要占据一定的空间,而且所需空间比本身的表还大,相当于用空间换取时间
(2)会降低增删改的效率

五、SQL解析

这里推荐这篇博客里面的内容讲得挺全的,看一下会有收获的。

我们在使用Mysql的时候,其实主要包括我们只负责编写sql语句,而sql语句的解析就由mysql内部自动解析,并且查询数据。

sql编写过程

select distinct ... from .. join ...on ...where...group by ... having ...order by... limit ...

sql解析过程

from... on ... join...where ...group by ...having ... select distinct ...order by ...limit...

可以看到sql的解析跟编写的顺序是不一样的。

六、锁机制

(1)行锁

当mysql处于高并发的情况下,容易出现某一行被锁住的情况,最经典的就是多个客户端同时操作某一行数据,客户端A操作这一行数据后,事务还未提交,客户端B又同时在操作该行数据,此时,由于客户端A在操作此行数据,而且事务还未提交,这个时候这行数据是被锁住的,客户端B如果同时操作该行数据就会进入阻塞状态,下面开始模拟行锁的情况。

两个客户端:客户端A Navicat,客户端B doc窗口的mysql

根据用户id更新数据
菜鸟的mysql进阶_第20张图片
客户端A操作id为1的这行数据,此时未提交事务,再用客户端B操作这一行数据

在这里插入图片描述
可以看到操作这一行数据时已经阻塞了,只有等客户端A的事务提交后,客户端B才会开始执行
菜鸟的mysql进阶_第21张图片

但是如果操作其他行数据呢?
在这里插入图片描述
可以看到操作其他行数据是没有问题的,这就是行锁,只锁定某一行数据。

(2)表锁

与行锁对应的还有表锁,顾名思义就是锁定整张表。下面开始模拟表锁的情况。

这次根据用户名更新用户,事务还未提交
菜鸟的mysql进阶_第22张图片
此时再操作该行数据,发现该行锁住了,进入了阻塞状态
在这里插入图片描述
那操作其他行数据呢,发现也是锁住状态,同样进入阻塞状态
在这里插入图片描述
注意,这里跟上面的模拟行锁有点不同,上面的行锁是根据用户id更新数据,而表锁这里是根据用户名更新用户,这就是问题所在,用户id是有主键索引,用户名没有,这就是说,索引会对锁产生影响,如果在有索引的情况下,数据库就只会锁住一行,即行锁,如果没有索引,则会锁住整张表,即表锁。

(3)范围锁

范围锁就是指锁定某一范围内的数据,下面开始模拟范围锁的情况。

更新id小于等于3的数据,未提交事务
菜鸟的mysql进阶_第23张图片
此时再分别操作id为1,2,3的数据行,发现都进入了阻塞状态
在这里插入图片描述
除此之外,如果操作id为4的数据也会阻塞,明明范围是小于等于3,可能这是它的范围锁的机制吧
在这里插入图片描述

如果操作id为5或6的数据行呢,发现id为5或6的数据行可以操作
在这里插入图片描述
这就是范围锁,因为更新的数据是id小于等于3,所以在id小于等于3(包括4都会阻塞)这个范围内的数据行都被锁住了,不在这个范围内的数据可以照常操作,这就是范围锁。

(4)悲观锁

悲观锁的定义就不用说了,做什么操作都必须加锁,这就是悲观锁,mysql实现悲观锁的方式可以通过for update语句实现。

格式select * from xxxxxxx where namemc='筛查' for update

for update实现了行级锁,一旦用户对某个行施加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行.如果其它用户想更新该表中的数据行,则也必须对该表施加行级锁.即使多个用户对一个表均使用了共享更新,但也不允许两个事务同时对一个表进行更新,真正对表进行更新时,是以独占方式锁表,一直到提交或复原该事务为止。行锁永远是独占方式锁。

只有当出现如下之一的条件,便释放共享更新锁:
(1)执行提交(COMMIT)语句;
(2)退出数据库(LOG OFF)
(3)程序停止运行。

悲观锁应用之高并发商品的抢购
悲观锁可以用来处理商品高并发的处理,虽然可以会影响性能,假设有多个人同时抢购一件商品,商品刚好剩一件,那么这个时候如果没做相应的处理,就会出现很严重的问题,这个时候的一种处理方式是加悲观锁。下面用一张用户表模拟一下悲观锁的使用。

先查询商品的库存量,在做现应的处理,这里就简单用一张用户表简单模拟一下
菜鸟的mysql进阶_第24张图片
加悲观锁后,发现加了悲观锁的数据行只能查询,不能更新
菜鸟的mysql进阶_第25张图片
只有提交了事务后,释放了锁后,才可以进行更新操作
在这里插入图片描述
在这里插入图片描述

这就是mysql的悲观锁的实现。

(5)乐观锁

与悲观锁相对的乐观锁,表示做什么操作都不加锁,有问题了再处理,大概就是这个意思,它的实现也很简单,只需要在表里面加个字段即可。
菜鸟的mysql进阶_第26张图片
字段version就可以用来实现乐观锁。

乐观锁应用之高并发商品的抢购
这里也是通过一张用户表来模拟,每次查询完库存后,更新操作时需要对应的版本号,同时版本号加1

假设两个用户同时抢购,同时执行了select * from user WHERE id = 1;查询库存量
菜鸟的mysql进阶_第27张图片
这个时候查询到的version同样是0,此时,不管那一个用户先抢到了商品,即执行了UPDATE user set name = 'Tonny',version = version+1 WHERE version = 0 and id =1;语句,版本号都进行了加1,version=1,这时另一个用户由于之前查询的version=0,执行update语句时按照之前的version=0来更新就会失败,因为商品已经被另一人买了,并且version已经等于1
菜鸟的mysql进阶_第28张图片
在这里插入图片描述

这就是mysql简单实现乐观锁的基本操作。

(6)读写锁

我们应该都明白这样的问题,针对数据库表中的数据,当同一时刻多个用户并发读取同一个数据时,不会出现任何问题,因为没有涉及对表中数据的增加删除和修改的操作,但是当多个用户需要在同一时刻对表内容进行增删改,或者一个用户在进行查询,另一个用户同时需要增删改的情况下,对于读取信息的那个用户来说,就会发生前后读取内容不一致的问题,所以就需要针对这类的并发问题,就可以使用两种类型的锁系统来解决,分别称为共享锁(shared lock)和排它锁(exclusive lock),或者也可以叫做读锁(read lock)和写锁(write lock)。

  先简单解释一下这两种锁的概念,读锁是共享的,是异步的,是相互不阻塞的,高并发下,多个用户同时读取数据库中的同一个资源,可以相互不干扰;而写锁是排他的,同步的,是会阻塞其他的用户的写锁与读锁,只有这样才能保证,在一个时间段内,只有一个用户在对数据库进行操作,从而防止了其他用户访问到了正在修改的数据。

读锁
读锁:是一种共享锁,一个事务持有读锁时,不会阻塞其它的读锁,其他事务都可以对该数据进行读取;

加上读锁后,经过测试,只能查询,增删改均失败,同时在并发的情况下,加上读锁,其余线程对该表的操作也只能查询,增删改则会阻塞。
菜鸟的mysql进阶_第29张图片

菜鸟的mysql进阶_第30张图片
解锁更新成功
菜鸟的mysql进阶_第31张图片

写锁
写锁:是一种排他锁,一个锁持有写锁会阻塞其他的写锁和读锁,从而保证了一个只有一个事务进行写操作,并且防止其他事务读取正在写入资源,避免了脏读;

当持有写锁的一端,可以进行增删改查
菜鸟的mysql进阶_第32张图片
同时再打开一个mysql,在user被加上写锁的情况下,在其他端进行增删改查,发现都阻塞了

菜鸟的mysql进阶_第33张图片

七、SQL优化

据我了解,优化主要包括sql语句的优化和索引的优化,大多情况下索引优化是重点,这里先简单记录一下sql语句的优化,索引优化有点难度,需要定位慢sql,分析sql效率等,比较复杂,等有时间再去深入学习一下索引的优化。

(1)sql语句优化

1、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

2、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
如:

select id from t where num is null

可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

select id from t where num=0

3、where子句使用or的优化
通常使用 union all 或 union 的方式替换“or”会得到更好的效果。where子句中使用了or关键字,索引将被放弃使用。
如:

SELECT id FROM A WHERE num = 10 or num = 20;

建议改成

SELECT id FROM A WHERE num = 10 union all SELECT id FROM A WHERE num=20;

4、where子句中对字段进行表达式操作的优化
不要在where子句中的“=”左边进行函数、算数运算或其他表达式运算,否则系统将可能无法正确使用索引。
如:

select id from t where num/2=100

应改为:

select id from t where num=100*2

5、利用limit 1 、top 1 取得一行
对于确定要取的只有一行数据时,建议在sql语句后加上limit 1来终止[数据库索引]继续扫描整个表或索引。
如:

SELECT id FROM A LIKE 'abc%' 

应改为

SELECT id FROM A LIKE 'abc%' limit 1

6、任何情况都不要用 select * from table ,用具体的字段列表替换"*",不要返回用不到的字段,避免全盘扫描!

7、批量插入优化
如:

INSERT into person(name,age) values('A',24)
INSERT into person(name,age) values('B',24)
INSERT into person(name,age) values('C',24)

应改为

INSERT into person(name,age) values('A',24),('B',24),('C',24)

8、like语句的优化
反例

SELECT id FROM A WHERE name like '%abc%'

由于abc前面用了“%”,因此该查询必然走全表查询,除非必要(模糊查询需要包含abc),否则不要在关键词前加%

正例

SELECT id FROM A WHERE name like 'abc%'

9、Inner join 和 left join、right join、子查询

第一:inner join内连接也叫等值连接,left/right join是外连接。

SELECT A.id,A.name,B.id,B.name FROM A LEFT JOIN B ON A.id =B.id;

SELECT A.id,A.name,B.id,B.name FROM A RIGHT JOIN ON B A.id= B.id;

SELECT A.id,A.name,B.id,B.name FROM A INNER JOIN ON A.id =B.id;

经过来之多方面的证实 inner join性能比较快,因为inner join是等值连接,或许返回的行数比较少。但是我们要记得有些语句隐形的用到了等值连接,如:

SELECT A.id,A.name,B.id,B.name FROM A,B WHERE A.id = B.id;

推荐:能用inner join连接尽量使用inner join连接

第二:子查询的性能又比外连接性能慢,尽量用外连接来替换子查询。
反例

mysql是先对外表A执行全表查询,然后根据uuid逐次执行子查询,如果外层表是一个很大的表,我们可以想象查询性能会表现比这个更加糟糕。

Select* from A where exists (select * from B where id>=3000 and A.uuid=B.uuid);

执行时间:2s左右

正例

Select* from A inner join B ON A.uuid=B.uuid where b.uuid>=3000;  

这个语句执行测试不到一秒;
执行时间:1s不到

第三:使用JOIN时候,应该用小的结果驱动大的结果
left join 左边表结果尽量小,如果有条件应该放到左边先处理,right join同理反向。如:

反例

Select * from A left join B A.id=B.ref_id where  A.id>10

正例

select * from (select * from A wehre id >10) T1 left join B on T1.id=B.ref_id;

10、很多时候用 exists 代替 in 是一个好的选择
如:

select num from a where num in(select num from b)

用下面的语句替换:

select num from a where exists(select 1 from b where num=a.num)

11、字段的优化

第一:尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

第二:尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

你可能感兴趣的:(mysql)