1 页表
2 索引:按照关键字建立索引→按照关键字建立B+树(将数据复制一份)→查询时查到叶节点得到主键值再回表查询记录
3 联合索引:按照多个关键字建立索引(隐含了主键,当关键字相等时)→最左匹配原则(如果第一个关键字条件为*则全表查询)
4 最左匹配:查询时只用到了索引的前缀部分关键字进行索引查询,其他的关键字用过滤
5 优化:全表扫描or索引
6 索引,过滤,回表
7 条件部分:将字符转化为数字,如果字段是字符串,则全表扫描
8 事务:原子性(操作不可分割),隔离性(事务间不互相影响),一致性(数据库变化一致),持久性()日志,autocommit,隐式提交,回滚/保存点
9 读未提交read uncommitted(脏读,幻读,不可重复读),读已提交read committed(幻读,不可重复读),可重复读repeatable read(可能幻读),串行化serializable(对同一条记录操作是串行的)
10 读已提交:版本控制链readview,找到已提交的版本读,可以读自己修改的数据。
11 MVCC:读已提交,可重复读。读已提交在每一次select时生成readview,可重复读在第一次select时生成readview。
12 事务锁:读锁/共享锁,写锁/排他锁,只有读锁和读锁不冲突。
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
上述锁模式的兼容情况具体如下表所示。
InnoDB行锁模式兼容性列表
请求锁模式 是否兼容 当前锁模式 |
X | IX | S | IS |
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
间隙锁:范围锁
select会绕开锁
隐式锁:INSERT...SELECT...和 CREATE TABLE...SELECT...语句,可能会阻止对源表的并发更新,造成对源表锁的等待。如果查询比较复杂的话,会造成严重的性能问题,我们在应用中应尽量避免使用。实际上,MySQL将这种SQL叫作不确定(non-deterministic)的SQL,不推荐使用。
innodb加行锁的前提是:必须是通过索引条件来检索数据,否则会切换为表锁。确保没有行锁才加表锁。
13 乐观锁和悲观锁都是针对读(select)来说的。
案例:
某商品,用户购买后库存数应-1,而某两个或多个用户同时购买,此时三个执行程序均同时读得库存为“n”,之后进行了一些操作,最后将均执行update table set 库存数=n-1,那么,很显然这是错误的。
解决:
使用悲观锁(其实说白了也就是排他锁)
|-- 程序A在查询库存数时使用排他锁(select * from table where id=10 for update)
|-- 然后进行后续的操作,包括更新库存数,最后提交事务。
|-- 程序B在查询库存数时,如果A还未释放排他锁,它将等待……
|-- 程序C同B……
使用乐观锁(靠表设计和代码来实现)
|-- 一般是在该商品表添加version版本字段或者timestamp时间戳字段
|-- 程序A查询后,执行更新变成了:
update table set num=num-1 where id=10 and version=23
这样,保证了修改的数据是和它查询出来的数据是一致的(其他执行程序肯定未进行修改)。当然,如果更新失败,表示在更新操作之前,有其他执行程序已经更新了该库存数,那么就可以尝试重试来保证更新成功。为了尽可能避免更新失败,可以合理调整重试次数(阿里巴巴开发手册规定重试次数不低于三次)。
总结:对于以上,可以看得出来乐观锁和悲观锁的区别:
悲观锁实际使用了排他锁来实现(select **** for update)。文章开头说到,innodb加行锁的前提是:必须是通过索引条件来检索数据,否则会切换为表锁。
因此,悲观锁在未通过索引条件检索数据时,会锁定整张表。导致其他程序不允许“加锁的查询操作”,影响吞吐。故如果在查询居多的情况下,推荐使用乐观锁。
“加锁的查询操作”:加过排他锁的数据行在其他事务中是不能修改的,也不能通过for update或lock in share mode的加锁方式查询,但可以直接通过select ...from...查询数据,因为普通查询没有任何锁机制。
乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。
也就是一句话:读用乐观锁,写用悲观锁。