MySql的两大知名引擎:MyISAM 和 InnoDB
InnoDB的两大优势: 事务
行锁
(基于索引,与Oracle行锁不同)
并发控制的两种手段:
1. 锁
普通锁:所有行为都加一样的锁,串行执行,效率极低
共享锁(S锁,Share) 和 排它锁(X锁,eXclude):
读数据使用共享锁
,共享锁与共享锁不互斥,写数据使用排它锁
,排它锁与所有锁互斥
导致的结果是,读读并行,读写、写写不并行
2. 数据多版本
涉及到原版数据
和修改中的数据
当修改数据时,把原版数据
拷贝到一个地方做改动,也就是修改中的数据
读任务永远去读原版数据
,做到读写并行
重点
- 普通锁:完全不并行
- 共享锁排它锁:读读并行
- 数据多版本:读写并行
InnoDB引擎使用的就是数据多版本,实现高并发
具体细节:
1. 事务提交
时,不立即将数据刷到磁盘,而是写redo日志
数据刷到磁盘 - 随机写
redo日志 - 顺序写
扩展阅读:https://blog.csdn.net/u010087886/article/details/54405934
顺序写的效率远高于随机写,数据库会定期将redo日志里的内容更新到磁盘上,这样做可以极大提高效率,假如某个时间点,数据库突然宕机或被关闭了,再次启动时,redo日志就会像它的名字一样被再次执行以确保回到关闭前的状态
这里加上一些个人猜测的基础理解吧,数据肯定是要存磁盘的,但是启动的时候也会将一些表数据放在内存(并非查询缓存),对应着innodb_buffer_pool_size
参数设置的区域,操作数据时可以直接改内存里的,并且记录redo日志即可,反正查询也是先查内存,是最新数据,mysql定期将redo日志刷到磁盘(可以自己配置频率)
2. 事务执行
时,将对数据的增删改操作存入undo日志
undo日志存在回滚段
中,当事务rollback的时候,就依靠回滚段中的undo日志将数据库恢复到事务执行前的状态
InnoDB引擎会给每行数据加三个内部属性:
1. 最近一次修改它的事务ID
2. 指向undo日志的指针
3. 单调递增的行ID
InnoDB的各种锁:
隔离级别:默认RR
1. 自增锁:表锁
- 事务A插入一条数据自增ID是1,没提交
- 假如不加表锁,事务B此时也插入一条数据,ID自增为2,没提交
- 事务A再插一条数据,ID是3
- 事务A查询数据,查到ID是1和3,就会觉得很奇怪,连续插的两条数据怎么ID隔了一个
- 所以要加表锁哟
2. 意向锁
意向锁是表锁
,意向锁与意向锁之间是不冲突的
意向锁的作用比较难说清楚,这篇说的详细并且清楚:
扩展阅读:https://blog.csdn.net/arkblue/article/details/53895150
1. mysql中的表锁:
LOCK TABLE my_tabl_name READ; 用读锁锁表,会阻塞其他事务修改表数据。
LOCK TABLE my_table_name WRITe; 用写锁锁表,会阻塞其他事务读和写。
2.Innodb引擎支持行锁,行锁分为
共享锁,一个事务对一行的共享只读锁。
排它锁,一个事务对一行的排他读写锁。
3.这两中类型的锁共存的问题
考虑这个例子:
事务A锁住了表中的一行,让这一行只能读,不能写。
之后,事务B申请整个表的写锁。
如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。
数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。
数据库要怎么判断这个冲突呢?
step1:判断表是否已被其他事务用表锁锁表
step2:判断表中的每一行是否已被行锁锁住。
注意step2,这样的判断方法效率实在不高,因为需要遍历整个表。
于是就有了意向锁。
在意向锁存在的情况下,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。
在意向锁存在的情况下,上面的判断可以改成
step1:不变
step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。
注意:申请意向锁的动作是数据库完成的,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。
3. 插入意向锁
行锁
这种锁要与自增锁
区分开来,这种锁针对不带自增列的表
这种锁还要与意向锁
区分开来,这种锁针对数据插入,而不是更新或删除,而且是行锁不是表锁
4. 记录锁(行锁)
就是锁住某一条记录
select * from t where id=1 for update;
update t set name='hugeo' where id = 1
都会在id=1的索引记录上加锁,以阻止其他事务插入,更新,删除id=1的这一行。
加记录锁的时候会自动先给表加上意向锁。
5. 间隙锁
锁住间隙,例如:
select * from t where id between 8 and 15 for update
当事务A锁住8到15的范围之后,其他事务不能插入8-15之间的数据,否则如果事务A再次查询就会跟第一次查询结果不一致的,也就不满足不可重复读
了,所以当隔离级别低于RR的时候,例如ReadCommit,间隙锁就会失效
间隙锁本身是只锁间隙不锁已存在的数据本身的,只锁间隙意味着只限制insert语句,因为你无法删改不存在的数据
有个暂时不理解的问题,当只对一条记录加锁的时候也会锁间隙,不知道为什么,例如下表:
id是主键,number没加索引
当事务A执行:select * from gaptest where number=4 for update;
还未提交
事务B执行:insert into gaptest value(2,2);
就会阻塞
这是因为事务A将(1, 2)
到(3, 4)
以及(3, 4)
到(6, 5)
之间都用间隙锁锁住了,(2, 2)就无法插入了,但是不明白为什么,因为插入成功也不会导致不可重复读啊
6. 临键锁(next-key锁)
其实就是记录锁
+间隙锁
,即锁定一个范围,并且锁定记录本身,InnoDB默认加锁方式是next-key锁。
MySQL索引,B+树
聚集索引:
- 每张表只能有一个聚集索引。
主键
->第一个NonUnique列
->InnoDB自己创建的隐藏的RowID列
- B+树的叶子节点直接存储行数据
非聚集索引
- 普通索引
- B+树的叶子节点存PK值,所以通过非聚集索引找数据要扫描2次