事务
查看事务隔离级别: select @@tx_isolation
修改会话级别的事务隔离级别:set tx_isolation='READ-UNCOMMITTED'
脏读:一个事务读取到另一个事务未提交的数据
不可重复读:同一个事物中两次读取得到不同的结果
幻读:某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。
读未提交:这种隔离级别存在脏读的可能性。
修改事务隔离级别:set tx_isolation='READ-UNCOMMITTED'
脏读现象:两个会话同时开启事务,事务1查询,事务2修改,此时事务1和事务2都未提交事务,事务1再次查询能够查询到事务2修改后的数据。
读已提交:解决了脏读,但存在不可重复读
修改事务隔离级别:set tx_isolation='READ-COMMITTED'
不可重复读现象:两个会话同时开启事务,事务1查询,事务2修改,此时事务2提交事务,事务1再次查询能够查询到事务2修改后的数据
可重复读:解决了脏读和不可重复读,但存在幻读
幻读现象:两个会话同时开启事务,事务1查询,事务2插入id为3的记录并提交(其实相当于此操作没有加事务),事务1再次查询和之前一样,没有id为3的记录,但此时插入事务为3的记录却报错
串行化: 解决幻读,两个会话中,会话1开启了事务,此时会话2可以查询,但是插入修改必须等待会话1中的事务提交后才能执行
存储引擎
INNODB 逻辑存储结构
表空间-》段-》区-》页-》行
默认一页大小是16KB
默认一区大小是1M
一区有64页
INNODB和MYISAM 区别
INNODB支持事务,行锁 外键
索引
5阶的B树
5阶的B+树
为什么InnoDB采用B+树作为数据的索引结构?
-
相比较二叉树,层级更少,搜索效率更高
-
相比较B树,一页的大小是固定的,一页就是一个节点,B树的每个结点都存储了key和data,B+树的data存储在叶子节点上。节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以IO操作次数更少。树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录 由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
-
相比较哈希索引 B+树支持范围查询和排序
实际能够创建的索引分类
-
主键索引 建立主键,会自动创建该索引,只能有一个
-
唯一索引 避免表中的列重复和非空所创建的索引,可以有多个,创建唯一约束时会自动创建该索引
-
常规索引 快速定位数据,可以有多个
在InnoDB中根据索引的存储形式分类
- 聚簇索引 索引结构的叶子节点存放行数据 必须有,而且只有一个
如果有主键,主键索引即是聚簇索引
如果没有主键,第一个唯一索引即是聚簇索引
如果没有唯一索引,会自动生成rowID列作为聚簇索引
- 非聚簇索引 索引的叶子节点存放的是对应的主键 可以有多个
一张表中的索引图如下:
InnoDB中树的高度怎么计算
联合索引
expain Mysql中explain各字段的解释_mameng1988的博客-CSDN博客
举例:创建一个(a,b)的联合索引,那么它的索引树就是下图的样子。
\
\
可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。但是我们又可发现a在等值的情况下,b值又是按顺序排列的,但是这种顺序是相对的。
索引失效的场景:
-
索引列上加函数操作 需要注意的是,优化器并不是要放弃使用这个索引。 有可能只是降低type级别
-
字符串不加单引号,相当于做了函数运算
-
联合索引 最左匹配原则,查询从索引最左侧开始,并且跳过中间列,后面的索引将失效,范围查询> < 后面的列失效
-
模糊查询,前半部分中精确的话不会失效,后半部分精确的话会失效
-
用or链接的子句 必选都是索引列 索引才会生效
-
数据分布情况 如果优化器认为走索引还不如全表扫描来的快,将不走索引,因为走索引会有回表操作 覆盖索引
select 所需要查询的列尽量在一个索引树上,这样能够避免回表查询
select name,password from user where name='a';
这条sql 最优应该建立 name,password联合索引,比建立name单列索引要高效。
where order by group by 后的条件建立索引
批量插入sql
insert into user values(1,'mxx'),(2,'wqq'),(3,'ass');
建立索引时候可以指定索引排序是升序还是降序
create idx_name_age_ad on user (age asc ,name desc);
order by 条件使用索引生效的前提是使用了覆盖索引,如果select * 将不会走索引
limit 分页查询优化
explain select * from user_test ut limit 100,100 全表扫描
explain select s.* from user_test s right join (select id from user_test ut limit 100,100) a on s.id=a.id 利用覆盖索引优化查询
select count(name) from user 只会累加name 不会null的记录
innodb 行锁是针对索引加的锁,并不是针对记录加的锁,所以update 尽量where条件加上索引列,否则会升级到表锁
锁
乐观锁和悲观锁是两种思想,根据两种思想有不同的实现,两种锁都能达到锁的效果,但是不同场景下带来的效率不一样。乐观锁不会每次都会上锁,只有当更新操作时候判断当前数据是否有其他线程占用,适用于读多写少场景,带来更高的吞吐量,如果读少写多,会不停的重试,悲观锁是每次操作数据都会上锁,适用于读少写多,如果读多写少的话会浪费资源。
Mysql乐观锁通常需要程序实现,增加版本号字段,当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录 的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数 据。实现参考:mysql乐观锁是什么?-mysql教程-PHP中文网
表级锁
表读锁,表写锁
加了读锁 所有的客户端对表只能读,当前客户端写入报错,其他客户端写入阻塞
加了写锁 当前客户端能读能写,其他客户端读阻塞 写阻塞
什么时候使用表锁
对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个别特殊事务中,也可以考虑使用表级锁。
- 第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
- 第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表。
在InnoDB下 ,使用表锁要注意以下两点。\
(1)使用LOCK TALBES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层MySQL Server负责的,仅当autocommit=0、innodb_table_lock=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁;否则,InnoDB将无法自动检测并处理这种死锁。\
(2)在用LOCAK TABLES对InnoDB锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCAK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK不能释放用LOCAK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁,正确的方式见如下语句。
例如,如果需要写表t1并从表t读,可以按如下做:
SET AUTOCOMMIT=0;
LOCAK TABLES t1 WRITE, t2 READ, ...;
[do something ``with` `tables t1 ``and` `here];
COMMIT;
UNLOCK TABLES;
元数据锁
mysql 当执行增删改查语句时候会自动增加元数据读锁,alter table会加元数据写锁
意向锁
执行增删改,select * for update 会加上表的意向写锁,执行select * lock in share model 会加意向读锁,这样当这时加表锁的时候不用每行数据去遍历查询是否兼容,直接判断加表锁的类型是否和意向锁兼容,从而判断是否能加表读锁或者表写锁
行级锁
行级锁的单位是next-key lock(临键锁),在特定条件下(等值查询)会优化成行锁或者间隙锁
存在以下表,当然一下语句设置了不自动提交或在事务中执行
select * from user_test 不加锁
select * from user_test lock in share mode 不加where条件 升级到表锁
select * from where id=1 user_test lock in share mode 条件是唯一索引且条件值存在 加的是行读锁
假设每行数据有两个属性,共享锁和排他锁,如果两个线程进来,共享锁和共享锁能够兼容,共享锁和排他锁不能兼容,排他锁和排他锁不能兼容,insert update delete 加的都是排他锁,select不加锁,select .. lock in share mode 加的是共享锁,select 。。for update 加的是排他锁,注意,所有的加锁都是针对索引加的锁,要增删改查操作是否用到了索引,没用到会升级到表锁,insert语句是insert完后对索引加锁(应该是),不会升级到表锁
select * from where id=4 user_test lock in share mode 条件是唯一索引且条件值不存在 加的是间隙锁,将(5,6)间隙锁住
间隙锁(Gap Lock)是Innodb在可重复读提交下为了解决幻读问题时引入的锁机制(上了间隙锁,只有再插入的时候会等待),锁的是叶子节点之间的指针,间隙锁不分什么共享锁与排它锁,
select * from user_test where id=4 for update 条件是唯一索引且条件值不存在 加的是间隙锁,将(5,6)间隙锁住
select * from user_test where name ='d' for UPDATE 条件是非唯一索引且条件存在,加的是临键锁,锁的是(c,d)的间隙,d的行锁,(d,e)的间隙,此时另一会话插入insert into user_test values (8,"cc","aaaa")是插不进去的,会等待事务释放
select * from user_test where name ='cc' for UPDATE 锁的是(c,d)的间隙
注意一点 间隙锁是可以兼容的,但是会出现死锁 实践此点执行以下几步:
-
A客户端开始事务 B客户端开始事务
-
A客户端select * from user_test where id=4 for update
-
B客户端select * from user_test where id=4 for update 执行成功可以兼容
-
B insert into user_test values (4,"ccaccc","aaaa") 等待
-
A insert into user_test values (4,"ccaccc","aaaa") 等待
范围查询比较复杂 参考Next-key Lock的规则 - 知乎 (zhihu.com)
避免死锁
设置事务等待锁的超时时间。当一个事务的等待时间超过该值后,就对这个事务进行回滚,于是锁就释放了,另一个事务就可以继续执行了。在 InnoDB 中,参数 innodb_lock_wait_timeout 是用来设置超时时间的,默认值时 50 秒。
当发生超时后,就出现下面这个提示:
可以设置重试
更好的是从业务的角度来预防死锁
mvcc 看一遍就理解:MVCC原理详解 - 掘金 (juejin.cn)
binlog Binlog是记录所有数据库表结构变更以及表数据修改的二进制日志,不会记录SELECT和SHOW这类操作。逻辑日志 作用是主从复制 数据恢复 记录默认为ROW级别
undolog 逻辑日志,执行一条delete,记录一条insert 在事务开始时产生,事务提交时不会立即删除,还可能用于mvcc,作用是事务的原子性,mvcc