MySQL的RR隔离级别与幻读问题 最近在网上看了不少mysql锁的文章,不少文章都提到InnoDB的RR隔离级别(Repeatable Read)无法解决幻读的问题 。对此问题作者亲自做了一些实验 , 将实验结论记录在此 。
本次实验的mysql版本为5.7.22。
此篇文章的重点在于通过实验的形式解释清楚InnoDB的RR隔离级别是否解决了幻读问题 。所以文中将不会对一些相关的概念进行解释 , 默认读者已经具备相关知识 。如果读者对于以下的知识点不甚清楚 , 最好自行查阅相关资料,理解清楚之后再阅读接下来的实验内容,以免造成困惑 。
进行此次实验需要具备的知识点(包括但不限于):
创建表结构:
创建两条数据:
最终的表数据如下:
打开两个终端,连上mysql,分别启动事务a和事务b 。
在事务a和事务b上面分别执行如下命令:
查询出来的结果如下:
事务a:
事务b:
很明显事务b没有查询到事务a未提交的新插入数据 。原因也很简单,因为 普通的select语句是快照读,而事务b启动时,它的快照数据就已经被版本锁定了。
那么我们在事务b里面执行如下命令来看看执行结果:
执行完成之后我们发现事务b此时会block住,原因是 事务a的insert语句排它锁住了id为3的新插入数据,而事务b想请求所有行的共享锁,肯定是需要等待的 。
那么此时事务b当前读id为1或2的数据(非事务a新插入数据)是否可行呢?
结论是可行的 , 因为 tmp_table 存在唯一键,且事务a的insert语句只是锁住了id为3的行 。所以其他事务获取其他行的共享锁是可行的。读者可以自行测试 , 这里就不做演示了 。
事务a和事务b执行如下命令:
事务b打印的结果:
还是一样 , 因为普通select是快照读,事务b还是读取到的是快照数据,所以不包含事务a提交之后的新数据。
【mysql隔离级怎么加锁 mysql隔离级别实现】 让我们在事务b下面使用共享锁查看当前版本数据:
结果如下:
可以查询到事务a已提交的新数据,所以此时使用当前读就产生了幻读。
还有另一种情况也会产生幻读,并且只需要执行普通的select语句 。下面请看演示 。
在事务b下面执行如下两条语句:
第一条命令使用update更新了事务a已提交的新数据,第二条命令通过普通的select语句查看快照数据 。
打印结果如下:
可以看到事务a已提交的新数据被事务b使用update语句更新了,并且通过普通的select语句给查询出来了 , 很显然,出现了幻读。
所以说InnoDB的RR隔离级别没有或者解决了幻读问题都不太准确 。应该说它并没有完全解决幻读的问题 。
如果在同一个事务里面,只是总是执行普通的select快照读,是不会产生幻读的 。
但是如果在这个事务里面通过当前读或者先更新然后快照读的形式来读取数据,就会产生幻读 。
Phantom Rows
Innodb 中 RR 隔离级别能否防止幻读?
简述mysql的事务隔离级别有哪些MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 。我们可以通过SELECT @@tx_isolation;命令来查看 。
需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 REPEATABLE-READ(可重读) 事务隔离级别下使用的是Next-Key Lock 锁算法 , 因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)
是不同的 。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要求,即达到了
SQL标准的 SERIALIZABLE(可串行化) 隔离级别 。因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 READ-COMMITTED(读取提交内容) , 但是你要知道的是InnoDB 存储引擎默认使用 REPEAaTABLE-READ(可重读) 并不会有任何性能损失 。
InnoDB 存储引擎在 分布式事务 的情况下一般会用到 SERIALIZABLE(可串行化) 隔离级别 。
Mysql:RR隔离级别下的幻读众所周知,Mysql在InnoDB下有四种隔离级别mysql隔离级怎么加锁:
未提交读(Read Uncommitted)
提交后读(Read Committed)
可重复读(Repeatable Read)
串行化(Serializable)
其中可重复读(RR)可以避免脏读( a事务读到b事务回滚前mysql隔离级怎么加锁的数据)以及可不重复读( a事务在b事务修改提交的前后,两次分别读到的数据不一致) 。但是对于幻读(a事务在b事务insert提交前后,两次分别读到的数据不一致),却存在争议 。
下面我们来做一个试验:
对于下面这张简单的数据表
idnum
111
222
333
我们开启a、b两个事务
a事务b事务
beginbegin
select * from tb----
----insert into tb (id,num)values(4,44)
----commit
select * from tb----
commit
试验结果:a事务的两次select查询到的结果相同,在后一次查询中没有返回新插入id=4的那条记录 。
据此,很多人判断说RR隔离级别下“不存在”幻读 。
但果真如此吗?----
出现上面的试验结果,是因为在RR隔离级别事务下,Mysql会对前一次select的结果快照 。所以第二次select其实是快照读(这也正是RR隔离级别下能够避免不可重复读的策略) 。
如果我们把试验条件稍作修改,同样开启a、b两个事务:
a事务b事务
beginbegin
select * from tb----
----insert into tb (id,num)values(5,55)
----commit
update tb set num=num 1----#此处a事务做一次修改操作
select * from tb----
commit
试验结果:在a事务的第二次select中出现mysql隔离级怎么加锁了b事务新插入的id=5的记录 。
由于做了update操作,之前的快照失效了 , 所以说RR隔离级别下的快照策略并没能真正避免幻读 。
ps. 假如给第二次的select查询上锁(无论是共享锁还是排它锁),也会得到同样的结果,都会令快照失效 。
MySQL简单介绍——换个角度认识MySQL1、InnoDB存储引擎
Mysql版本=5.5 默认的存储引擎,MySQL推荐使用的存储引擎 。支持事务,行级锁定,外键约束 。事务安全型存储引擎 。更加注重数据的完整性和安全性 。
存储格式 : 数据 , 索引集中存储 , 存储于同一个表空间文件中 。
InnoDB的行锁模式及其加锁方法: InnoDB中有以下两种类型的行锁:共享锁(读锁: 允许事务对一条行数据进行读?。┖?互斥锁(写锁: 允许事务对一条行数据进行删除或更新),对于update,insert,delete语句 , InnoDB会自动给设计的数据集加互斥锁,对于普通的select语句 , InnoDB不会加任何锁 。
InnoDB行锁的实现方式: InnoDB行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来对记录加锁 。InnoDB这种行锁实现特点意味着:如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟表锁一样 。
(1)在不通过索引条件查询时,InnoDB会锁定表中的所有记录 。
(2)Mysql的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果使用相同的索引键 , 是会出现冲突的 。
(3)当表有多个索引的时候 , 不同的事务可以使用不同的索引锁定不同的行,但都是通过行锁来对数据加锁 。
优点:
1、支持事务处理、ACID事务特性;
2、实现了SQL标准的四种隔离级别( 原子性( Atomicity )、一致性( Consistency )、隔离性(Isolation )和持续性(Durability ));
3、支持行级锁和外键约束;
4、可以利用事务日志进行数据恢复 。
5、锁级别为行锁 , 行锁优点是适用于高并发的频繁表修改,高并发是性能优于 MyISAM 。缺点是系统消耗较大 。
6、索引不仅缓存自身,也缓存数据,相比 MyISAM 需要更大的内存 。
缺点:
因为它没有保存表的行数,当使用COUNT统计时会扫描全表 。
使用场景:
(1)可靠性要求比较高,或者要求事务;(2)表更新和查询都相当的频繁,并且表锁定的机会比较大的情况 。
2、 MyISAM存储引擎
MySQL= 5.5 MySQL默认的存储引擎 。ISAM:Indexed Sequential Access Method(索引顺序存取方法)的缩写,是一种文件系统 。擅长与处理,高速读与写 。
功能:
(1)支持数据压缩存储,但压缩后的表变成了只读表,不可写;如果需要更新数据,则需要先解压后更新 。
(2)支持表级锁定,不支持高并发;
(3)支持并发插入 。写操作中的插入操作,不会阻塞读操作(其他操作);
优点:
1.高性能读?。?
2.因为它保存了表的行数 , 当使用COUNT统计时不会扫描全表;
缺点:
1、锁级别为表锁,表锁优点是开销小,加锁快;缺点是锁粒度大,发生锁冲动概率较高,容纳并发能力低,这个引擎适合查询为主的业务 。
2、此引擎不支持事务 , 也不支持外键 。
3、INSERT和UPDATE操作需要锁定整个表;
使用场景:
(1)做很多count 的计算;(2)插入不频繁 , 查询非常频繁;(3)没有事务 。
InnoDB和MyISAM一些细节上的差别:
1、InnoDB不支持FULLTEXT类型的索引,MySQL5.6之后已经支持(实验性) 。
2、InnoDB中不保存表的 具体行数,也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可 。注意的是,当count()语句包含 where条件时,两种表的操作是一样的 。
3、对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引 , 但是在MyISAM表中,可以和其他字段一起建立联合索引 。
4、DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除 。
5、LOAD TABLE FROM MASTER操作对InnoDB是不起作用的 , 解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用 。
6、另外 , InnoDB表的行锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围 , InnoDB表同样会锁全表 。
1.索引概述
利用关键字 , 就是记录的部分数据(某个字段 , 某些字段 , 某个字段的一部分) , 建立与记录位置的对应关系,就是索引 。索引的关键字一定是排序的 。索引本质上是表字段的有序子集,它是提高查询速度最有效的方法 。一个没有建立任何索引的表,就相当于一本没有目录的书 , 在每次查询时就会进行全表扫描,这样会导致查询效率极低、速度也极慢 。如果建立索引,那么就好比一本添加的目录,通过目录的指引,迅速翻阅到指定的章节 , 提升的查询性能,节约了查询资源 。
2.索引种类
从索引的定义方式和用途中来看:主键索引,唯一索引,普通索引,全文索引 。
无论任何类型 , 都是通过建立关键字与位置的对应关系来实现的 。索引是通过关键字找对应的记录的地址 。
以上类型的差异:对索引关键字的要求不同 。
关键字:记录的部分数据(某个字段,某些字段,某个字段的一部分) 。
普通索引,index:对关键字没有要求 。
唯一索引,unique index:要求关键字不能重复 。同时增加唯一约束 。
主键索引,primary key:要求关键字不能重复,也不能为NULL 。同时增加主键约束 。
全文索引,fulltext key:关键字的来源不是所有字段的数据,而是从字段中提取的特别关键词 。
PS:这里主键索引和唯一索引的区别在于:主键索引不能为空值,唯一索引允许空值;主键索引在一张表内只能创建一个 , 唯一索引可以创建多个 。主键索引肯定是唯一索引,但唯一索引不一定是主键索引 。
3.索引原则
如果索引不遵循使用原则,则可能导致索引无效 。
(1)列独立
如果需要某个字段上使用索引,则需要在字段参与的表达中,保证字段独立在一侧 。否则索引不会用到索引, 例如这条sql就不会用到索引:select * from A where id 1=10;
(2)左原则
Like:匹配模式必须要左边确定不能以通配符开头 。例如:select * from A where name like '%小明%',不会用到索引,而select * from A where name like '小明%' 就可以用到索引(name字段有建立索引),如果业务上需要用到'%小明%'这种方式,有两种方法:1.可以考虑全文索引,但mysql的全文索引不支持中文;2.只查询索引列或主键列,例如:select name from A where name like '%小明%' 或 select id from A where name like '%小明%' 或 select id,name from A where name like '%小明%' 这三种情况都会用到name的索引;
复合索引:一个索引关联多个字段,仅仅针对左边字段有效果,添加复合索引时,第一个字段很重要,只有包含第一个字段作为查询条件的情况才会使用复合索引(必须用到建索引时选择的第一个字段作为查询条件,其他字段的顺序无关),而且查询条件只能出现and拼接,不能用or,否则则无法使用索引.
(3)OR的使用
必须要保证 OR 两端的条件都存在可以用的索引,该查询才可以使用索引 。
(4)MySQL智能选择
即使满足了上面说原则,MySQL也能弃用索引,例如:select * from A where id1;这里弃用索引的主要原因:查询即使使用索引,会导致出现大量的随机IO,相对于从数据记录的第一条遍历到最后一条的顺序IO开销,还要大 。
4.索引的使用场景
(1)索引检索:检索数据时使用索引 。
(2)索引排序: 如果order by 排序需要的字段上存在索引,则可能使用到索引 。
(3)索引覆盖: 索引拥有的关键字内容,覆盖了查询所需要的全部数据 , 此时,就不需要在数据区获取数据,仅仅在索引区即可 。覆盖就是直接在索引区获取内容 , 而不需要在数据区获取 。例如: select name from A where name like '小明%';
建立索引索引时,不能仅仅考虑where检索,同时考虑其他的使用场景 。(在所有的where字段上增加索引,就是不合理的)
5.前缀索引
前缀索引是建立索引关键字一种方案 。通常会使用字段的整体作为索引关键字 。有时,即使使用字段前部分数据,也可以去识别某些记录 。就比如一个班级里,我要找王xx,假如姓王的只有1个人,那么就可以建一个关键字为'王'的前缀索引 。语法:Index `index_name` (`index_field`(N))使用index_name前N个字符建立的索引 。
6.索引失效
(1) 应尽量避免在 where 子句中使用 != 或操作符 , 否则将引擎放弃使用索引而进行全表扫描;
(2) 应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描;
(3) 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描;
(4)应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描;如select id from t where num/2 = 100;
(5) 应尽量避免在where子句中对字段进行函数操作 , 这将导致引擎放弃使用索引而进行全表扫描;如:select id from t where substring(name,1,3) = ’abc’ ;
(6)应尽量避免在where子句中对字段进行类型转换 , 这将导致引擎放弃使用索引而进行全表扫描; 如果列类型是字符串 , 那一定要在条件中将数据使用引号引用起来,如select id from t where id = 1;如果id字段在表设计中是varchar类型,那么即使id列上存的是数字,在查询时也一定要用varchar去匹配 , sql应改为select id from t where id = '1';
(7)应尽量避免在where子句中单独引用复合索引里非第一位置的索引;
join 的两种算法:BNL 和 NLJ
NLJ(Nested Loop Join)嵌套循环算法;以如下 SQL 为例:
select * from t1 join t2 on t1.a=t2.a
SQL 执行时内部流程是这样的:
1. 先从 t1(假设这里 t1 被选为驱动表)中取出一行数据 X;
2. 从 X 中取出关联字段 a 值,去 t2 中进行查找 , 满足条件的行取出;
3. 重复1、2步骤,直到表 t1 最后一行循环结束 。
这就是一个嵌套循环的过程,如果在被驱动表上查找数据时可以使用索引,总的对比计算次数等于驱动表满足 where 条件的行数 。假设这里 t1、t2都是1万行 , 则只需要 1万次计算 , 这里用到的是Index Nested-Loops Join(INLJ,基于索引的嵌套循环联接) 。
如果 t1、t2 的 a 字段都没有索引 , 还按照上述的嵌套循环流程查找数据呢?每次在被驱动表上查找数据时都是一次全表扫描,要做1万次全表扫描,扫描行数等于 1万 1万*1万 , 这个效率很低,如果表行数更多,扫描行数动辄几百亿,所以优化器肯定不会使用这样的算法,而是选择 BNL 算法;
BNLJ(Block Nested Loop Join)块嵌套循环算法;
1. 把 t1 表(假设这里 t1 被选为驱动表)满足条件的数据全部取出放到线程的 join buffer 中;
2. 每次取 t2 表一行数据 , 去 joinbuffer 中进行查找,满足条件的行取出,直到表 t2 最后一行循环结束 。
这个算法下,执行计划的 Extra 中会出现 Using join buffer(Block Nested Loop) , t1、t2 都做了一次全表扫描,总的扫描行数等于 1万 1万 。但是由于 joinbuffer 维护的是一个无序数组,每次在 joinbuffer 中查找都要遍历所有行,总的内存计算次数等于1万*1万 。另外如果 joinbuffer 不够大放不下驱动表的数据,则要分多次执行上面的流程 , 会导致被驱动表也做多次全表扫描 。
BNLJ相对于NLJ的优点在于 , 驱动层可以先将部分数据加载进buffer,这种方法的直接影响就是将大大减少内层循环的次数,提高join的效率 。
例如:
如果内层循环有100条记录,外层循环也有100条记录 , 这样的话,每次外层循环先将10条记录放到buffer中,内层循环的100条记录每条与这个buffer中的10条记录进行匹配,只需要匹配内层循环总记录数次即可结束一次循环(在这里,即只需要匹配100次即可结束),然后将匹配成功的记录连接后放入结果集中,接着,外层循环继续向buffer中放入10条记录 , 同理进行匹配 , 并将成功的记录连接后放入结果集 。后续循环以此类推,直到循环结束,将结果集发给client为止 。
可以发现,若用NLJ,则需要100 * 100次才可结束,BNLJ则需要100 / block_size * 100 = 10 * 100次就可结束,大大减少了循环次数 。
JOIN 按照功能大致分为如下三类:
JOIN、STRAIGHT_JOIN、INNER JOIN(内连接,或等值连接):取得两个表中存在连接匹配关系的记录 。
LEFT JOIN(左连接):取得左表(table1)完全记录,即是右表(table2)并无对应匹配记录 。
RIGHT JOIN(右连接):与 LEFT JOIN 相反,取得右表(table2)完全记录 , 即是左表(table1)并无匹配对应记录 。
注意:mysql不支持Full join,不过可以通过UNION 关键字来合并 LEFT JOIN 与 RIGHT JOIN来模拟FULL join 。
mysql 多表连接查询方式,因为mysql只支持NLJ算法,所以如果是小表驱动大表则效率更高;反之则效率下降;因此mysql对内连接或等值连接的方式做了一个优化,会去判断join表的数据行大小,然后取数据行小的表为驱动表 。
INNER JOIN、JOIN、WHERE等值连接和STRAIGHT_JOIN都能表示内连接,那平时如何选择呢?一般情况下用INNER JOIN、JOIN或者WHERE等值连接,因为MySQL 会按照"小表驱动大表的策略"进行优化 。当出现需要排序时,才考虑用STRAIGHT_JOIN指定某张表为驱动表 。
两表JOIN优化
a.当无order by条件时,根据实际情况,使用left/right/inner join即可,根据explain优化 ;
b.当有order by条件时,如select * from a inner join b where 1=1 and other condition order by a.col;使用explain解释语句;
1)如果第一行的驱动表为a,则效率会非常高 , 无需优化;
2)否则,因为只能对驱动表字段直接排序的缘故,会出现using temporary,所以此时需要使用STRAIGHT_JOIN明确a为驱动表,来达到使用a.col上index的优化目的;或者使用left join且Where条件中不含b的过滤条件 , 此时的结果集为a的全集,而STRAIGHT_JOIN为inner join且使用a作为驱动表 。注:使用STRAIGHT_JOIN虽然不会using temporary , 但也不是一定就能提高效率,如果a表数据远远超过b表,那么有可能使用STRAIGHT_JOIN时比原来的sql效率更低,所以怎么使用STRAIGHT_JOIN , 还是要视情况而定 。
在使用left join(或right join)时,应该清楚的知道以下几点:
(1). on与 where的执行顺序
ON 条件(“A LEFT JOIN B ON 条件表达式”中的ON)用来决定如何从 B 表中检索数据行 。如果 B 表中没有任何一行数据匹配 ON 的条件,将会额外生成一行所有列为 NULL 的数据,在匹配阶段 WHERE 子句的条件都不会被使用 。仅在匹配阶段完成以后,WHERE 子句条件才会被使用 。它将从匹配阶段产生的数据中检索过滤 。
所以我们要注意:在使用Left (right) join的时候,一定要在先给出尽可能多的匹配满足条件,减少Where的执行 。
(2).注意ON 子句和 WHERE 子句的不同
即使右表的数据不满足ON后面的条件,也会在结果集拼接一条为NULL的数据行,但WHERE后面的条件不一样,右表不满足WHERE的条件,左表关联的数据也会被过滤掉 。
(3).尽量避免子查询 , 而用join
往往性能这玩意儿,更多时候体现在数据量比较大的时候,此时,我们应该避免复杂的子查询 。
(1)in 和 not in 要慎用,如:select id from t where num in(1,2,3)对于连续的数值,能用 between 就不要用 in:select id from t where num between 1 and 3很多时候用 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)
(2)Update 语句 , 如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志 。
(3)join语句,MySQL里面的join是用小表去驱动大表,而由于MySQL join实现的原理就是做循环,比如left join就是对左边的数据进行循环去驱动右边的表,左边有m条记录匹配,右边有n条记录那么就是做m次循环,每次扫描n行数据,总扫面行数是m*n行数据 。左边返回的结果集的大小就决定了循环的次数,故单纯的用小表去驱动大表不一定的正确的,小表的结果集可能也大于大表的结果集,所以写join的时候尽可能的先估计两张表的可能结果集,用小结果集去驱动大结果集.值得注意的是在使用left/right join的时候,从表的条件应写在on之后,主表应写在where之后.否则MySQL会当作普通的连表查询;
(4)select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的;
(5)select * from t 这种语句要尽量避免,使用具体的字段代替*,更有实际意义,需要什么字段就返回什么字段;
(6)数据量大的情况下,limit要慎用,因为使用limit m,n方式分页时,mysql每次都是查询前m n条,然后舍弃前m条,所以m越大,偏移量越大,性能就越差 。比如:select * from A limit 1000000,20这钟 , 查询效率就会非常低,当分页的页数大于一定的数量之后,就可以换种方式来分页:select * from A a join (select id from A limit 1000000 , 20) b on a.id=b.id;
事务和锁机制是什么关系? 开启事务就自动加锁了吗? 菜鸟,谢谢了 。1、事务与锁是不同的 。事务具有ACID(原子性、一致性、隔离性和持久性),锁是用于解决隔离性的一种机制 。
2、事务的隔离级别通过锁的机制来实现 。另外锁有不同的粒度,同时事务也是有不同的隔离级别的 。
3、开启事务就自动加锁 。
ql规范定义的事务的隔离级别:
1.READ UNCOMMITTED(读取未提交内容)
所有事务可以看到未提交事务的执行结果,本隔离级别很少用到实际应用中,读取未提交的数据,又称为“脏读” 。
2.READ COMMITTED(读取提交内容)
大多数数据库的默认隔离级别是此级别,但不是MySQL默认的 。一个事务在开始的时候只能看见已提交事务所做的改变 。一个事务从开始到提交前所做的任何改变都是不可见的,除非提交 。这种隔离级别也称为不可重复读 。
3.REPEATABLE READ(可重复读)
此隔离级别是为了解决可重复读隔离级别导致的问题即一个事务多个实例并发读取数据时会看到不同的结果 。此隔离级别不会看到其他事务提交后的结果 , 即事务即使提交了我也看不到 。此级别也称为“幻读” 。
4.SERIALIZABLE(可串行化)
可串行化是最高的隔离级别 , 它通过强制事务排序 , 使之不可重读,解决了幻读的问题 。此隔离级别会在每个读的数据行上加共享锁,使用这种隔离级别会产生大量的超时现象,一般实际开发中不会用到 。
mysql加锁机制:
根据类型可分为共享锁(SHARED LOCK)和排他锁(EXCLUSIVE LOCK)或者叫读锁(READ LOCK)和写锁(WRITE LOCK) 。
根据粒度划分又分表锁和行锁 。表锁由数据库服务器实现 , 行锁由存储引擎实现 。
mysql提供了3种事务型存储引擎,InnDB、NDB Cluster和Falcon 。
一个事务执行的任何过程中都可以获得锁,但是只有事务提交或回滚的时候才释放这些锁 。这些都是隐式锁定,也可以显式锁定,InnoDB支持显式锁定,例如:
SELECT .... LOCK IN SHARE MODE (加共享锁)
SELECT .....FOR UPDATE(加排他锁)
多版本并发控制(重要):
Mysql的事务存储引擎不是简单实用行加锁机制,而是叫多版本并发控制(MVCC)技术,和行加锁机制关联实用 。以便应对更高的并发,当然是以消耗性能作为代价 。
每种存储引擎对MVCC的实现方式不同,InnoDB引擎的简单实现方式如下:
InnoDB通过为每个数据航增加两个隐含值的方式来实现 。这两个隐含值记录了行的创建时间,以及过期时间 。每一行存储事件发生时的系统版本号 。每一次开始一个新事务时版本号会自动加1,每个事务都会保存开始时的版本号,每个查询根据事务的版本号来查询结果 。
MySQL使用以下几种机制进行隔离性的实现:a.锁机制通过使用加锁机制,使用其它事务无法到读某事务末提交前的数据更新,解决脏读问题;mySQL 有:共享锁 , 排他锁 , 根据粒度,有行锁,表锁 。b.MVCC机制:事务存储引擎使用多版本并发控制(MVCC)技术,和行加锁机制关联使用MySQL 的InnoDB,XtraDB 引擎通过 使用MVCC 来解决幻读问题 。
「春招系列」MySQL面试核心25问(附答案) 篇幅所限本文只写mysql隔离级怎么加锁了MySQL25题,像其他mysql隔离级怎么加锁的Redis,SSM框架,算法,计网等技术栈mysql隔离级怎么加锁的面试题后面会持续更新,个人整理的1000余道面试八股文会放在文末给大家白嫖 , 最近有面试需要刷题的同学可以直接翻到文末领取 。
如果表使用自增主键 , 那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页 。如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机 , 因此每次新纪录都要被插到现有索引页得中间某个位置,频繁的移动、分页操作造成mysql隔离级怎么加锁了大量的碎片 , 得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE(optimize table)来重建表并优化填充页面 。
Server层按顺序执行sql的步骤为:
简单概括:
可以分为服务层和存储引擎层两部分,其中:
服务层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现 , 比如存储过程、触发器、视图等 。
存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎 。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认的存储引擎 。
Drop、Delete、Truncate都表示删除,但是三者有一些差别:
Delete用来删除表的全部或者一部分数据行,执行Delete之后,用户需要提交(commmit)或者回滚(rollback)来执行删除或者撤销删除,会触发这个表上所有的delete触发器 。
Truncate删除表中的所有数据,这个操作不能回滚,也不会触发这个表上的触发器,TRUNCATE比Delete更快 , 占用的空间更小 。
Drop命令从数据库中删除表,所有的数据行,索引和权限也会被删除,所有的DML触发器也不会被触发,这个命令也不能回滚 。
因此,在不再需要一张表的时候,用Drop;在想删除部分数据行时候,用Delete;在保留表而删除所有数据的时候用Truncate 。
隔离级别脏读不可重复读幻影读READ-UNCOMMITTED 未提交读READ-COMMITTED 提交读REPEATABLE-READ 重复读SERIALIZABLE 可串行化读
MySQL InnoDB 存储引擎的默认支持的隔离级别是REPEATABLE-READ(可重读)
这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别 下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生 , 这与其他数据库系统(如 SQL Server)是不同的 。所以 说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要 求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别 。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内 容):,但是你要知道的是InnoDB 存储引擎默认使用REPEATABLE-READ(可重读)并不会有任何性能损失。
InnoDB 存储引擎在分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别 。
主要原因:B 树只要遍历叶子节点就可以实现整棵树的遍历 , 而且在数据库中基于范围的查询是非常频繁的,而B树只能中序遍历所有节点 , 效率太低 。
文件与数据库都是需要较大的存储 , 也就是说 , 它们都不可能全部存储在内存中,故需要存储到磁盘上 。而所谓索引,则为了数据的快速定位与查找 , 那么索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数,因此B 树相比B树更为合适 。数据库系统巧妙利用了局部性原理与磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入,而红黑树这种结构,高度明显要深的多,并且由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性 。
最重要的是 , B 树还有一个最大的好处:方便扫库 。
B树必须用中序遍历的方法按序扫库,而B 树直接从叶子结点挨个扫一遍就完了,B 树支持range-query非常方便 , 而B树不支持,这是数据库选用B 树的最主要原因 。
B 树查找效率更加稳定 , B树有可能在中间节点找到数据,稳定性不够 。
B tree的磁盘读写代价更低:B tree的内部结点并没有指向关键字具体信息的指针(红色部分) , 因此其内部结点相对B 树更小 。如果把所有同一内部结点的关键字存放在同一块盘中,那么盘块所能容纳的关键字数量也越多 。一次性读入内存中的需要查找的关键字也就越多,相对来说IO读写次数也就降低了;
B tree的查询效率更加稳定:由于内部结点并不是最终指向文件内容的结点 , 而只是叶子结点中关键字的索引,所以,任何关键字的查找必须走一条从根结点到叶子结点的路 。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当;
视图是一种虚拟的表 , 通常是有一个表或者多个表的行或列的子集,具有和物理表相同的功能 游标是对查询出来的结果集作为一个单元来有效的处理 。一般不使用游标,但是需要逐条处理数据的时候,游标显得十分重要 。
而在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入 。当事务已经被提交之后 , 就无法再次回滚了 。
回滚日志作用:1)能够在发生错误或者用户执行 ROLLBACK 时提供回滚相关的信息 2) 在整个系统发生崩溃、数据库进程直接被杀死后,当用户再次启动数据库进程时,还能够立刻通过查询回滚日志将之前未完成的事务进行回滚,这也就需要回滚日志必须先于数据持久化到磁盘上,是mysql隔离级怎么加锁我们需要先写日志后写数据库的主要原因 。
InnoDB
MyISAM
总结
数据库并发会带来脏读、幻读、丢弃更改、不可重复读这四个常见问题 , 其中:
脏读:在第一个修改事务和读取事务进行的时候,读取事务读到的数据为100,这是修改之后的数据,但是之后该事务满足一致性等特性而做了回滚操作 , 那么读取事务得到的结果就是脏数据了 。
幻读:一般是T1在某个范围内进行修改操作(增加或者删除),而T2读取该范围导致读到的数据是修改之间的了 , 强调范围 。
丢弃修改:两个写事务T1 T2同时对A=0进行递增操作,结果T2覆盖T1,导致最终结果是1 而不是2,事务被覆盖
不可重复读:T2 读取一个数据,然后T1 对该数据做了修改 。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同 。
第一个事务首先读取var变量为50 , 接着准备更新为100的时,并未提交,第二个事务已经读取var为100,此时第一个事务做了回滚 。最终第二个事务读取的var和数据库的var不一样 。
T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据 , 此时读取的结果和和第一次读取的结果不同 。
T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改 。例如:事务1读取某表中的数据A=50,事务2也读取A=50,事务1修改A=A 50,事务2也修改A=A 50,最终结果A=100,事务1的修改被丢失 。
T2 读取一个数据,T1 对该数据做了修改 。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同 。
悲观锁 , 先获取锁,再进行业务操作,一般就是利用类似 SELECT … FOR UPDATE 这样的语句,对数据加锁,避免其他事务意外修改数据 。当数据库执行SELECT … FOR UPDATE时会获取被select中的数据行的行锁,select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用 。
乐观锁 , 先进行业务操作,只在最后实际更新数据时进行检查数据是否被更新过 。Java 并发包中的 AtomicFieldUpdater 类似 , 也是利用 CAS 机制,并不会对数据加锁,而是通过对比数据的时间戳或者版本号 , 来实现乐观锁需要的版本判断 。
分库与分表的目的在于,减小数据库的单库单表负担 , 提高查询性能,缩短查询时间 。
通过分表,可以减少数据库的单表负担,将压力分散到不同的表上,同时因为不同的表上的数据量少了 , 起到提高查询性能,缩短查询时间的作用,此外 , 可以很大的缓解表锁的问题 。分表策略可以归纳为垂直拆分和水平拆分:
水平分表:取模分表就属于随机分表,而时间维度分表则属于连续分表 。如何设计好垂直拆分 , 我的建议:将不常用的字段单独拆分到另外一张扩展表. 将大文本的字段单独拆分到另外一张扩展表, 将不经常修改的字段放在同一张表中,将经常改变的字段放在另一张表中 。对于海量用户场景,可以考虑取模分表,数据相对比较均匀,不容易出现热点和并发访问的瓶颈 。
库内分表,仅仅是解决了单表数据过大的问题 , 但并没有把单表的数据分散到不同的物理机上,因此并不能减轻 MySQL 服务器的压力,仍然存在同一个物理机上的资源竞争和瓶颈,包括 CPU、内存、磁盘 IO、网络带宽等 。
分库与分表带来的分布式困境与应对之策数据迁移与扩容问题----一般做法是通过程序先读出数据,然后按照指定的分表策略再将数据写入到各个分表中 。分页与排序问题----需要在不同的分表中将数据进行排序并返回,并将不同分表返回的结果集进行汇总和再次排序,最后再返回给用户 。
不可重复读的重点是修改,幻读的重点在于新增或者删除 。
视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据 。使用视图可以简化复杂的 sql 操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们 。
视图不能被索引,也不能有关联的触发器或默认值,如果视图本身内有order by 则对视图再次order by将被覆盖 。
创建视图:create view xxx as xxxx
对于某些视图比如未使用联结子查询分组聚集函数Distinct Union等 , 是可以对其更新的,对视图的更新将对基表进行更新;但是视图主要用于简化检索,保护数据 , 并不用于更新,而且大部分视图都不可以更新 。
B tree的磁盘读写代价更低 , B tree的查询效率更加稳定 数据库索引采用B 树而不是B树的主要原因:B 树只要遍历叶子节点就可以实现整棵树的遍历,而且在数据库中基于范围的查询是非常频繁的,而B树只能中序遍历所有节点,效率太低 。
B 树的特点
在最频繁使用的、用以缩小查询范围的字段,需要排序的字段上建立索引 。不宜:1)对于查询中很少涉及的列或者重复值比较多的列 2)对于一些特殊的数据类型 , 不宜建立索引 , 比如文本字段(text)等 。
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称 之为“覆盖索引” 。
我们知道在InnoDB存储引 擎中,如果不是主键索引,叶子节点存储的是主键 列值 。最终还是要“回表”,也就是要通过主键再查找一次,这样就 会比较慢 。覆盖索引就是把要查询出的列和索引是对应的 , 不做回表操作!
举例:
学号姓名性别年龄系别专业20020612李辉男20计算机软件开发20060613张明男18计算机软件开发20060614王小玉女19物理力学20060615李淑华女17生物动物学20060616赵静男21化学食品化学20060617赵静女20生物植物学
主键为候选键的子集,候选键为超键的子集,而外键的确定是相对于主键的 。
mysql隔离级怎么加锁的介绍就聊到这里吧,感谢你花时间阅读本站内容 , 更多关于mysql隔离级别实现、mysql隔离级怎么加锁的信息别忘了在本站进行查找喔 。
推荐阅读
- 射击游戏怎么调显卡,射击游戏画面
- 跨境电商如何多IP,跨境电商多平台运营实战基础
- 抖音带货卖家如何做推广,抖音带货卖家如何做推广赚钱
- redis实现登录注册模块vs,redis实现用户登录
- linux命令历史技巧 linux历史命令保存在哪里
- 如何营销kp客户,客户营销技巧的基本要求
- 蓝天照片怎么发视频号,发蓝天的照片是什么意思
- js事件处理的语句有哪两种,js中的常用事件
- c转换至vb.net vb 转c#