-
B+树
B+树是InnoDB底层采用的数据结构,它是一种多叉平衡搜索树。InnoDB底层采用的存储结构就是B+树。
它的优点:-
高度比较低,减少IO的次数
因为B+树采用是多叉树,所以在节点相同的情况下,比正常的二叉树的高度低了很多。
对于数据库来说,最耗时的操作就是从磁盘加载数据,因此想要提高数据库的效率,必须尽量减少从磁盘加载数据的次数。而对于多叉树,父子节点往往相距比较远,因此在数据量比较大的时候,一次加载到父子节点几乎是不可能的,最好的情况就是一次加载一个节点的数据,然后找到下一个节点进行加载,直到最后找到对应的信息。
从磁盘加载信息的时候,最耗时的操作是寻道,而从磁盘读取数据到数据总线的耗时几乎可以忽略,所以磁盘读取数据的时候会有一个预读的过程。也就是说,尽管你只要磁盘某个地方的一小段信息,加载的时候也会读取满一整个页的信息。所以,一个节点的大小设置成一个页大小是最合适的,一次io可以加载进一个节点的所有信息,然后再决定下一个节点。
B+树 -
叶子节点有兄弟指针,方便遍历
有些情况下,会出现需要全表扫描,因为B+树的所有信息都存在叶子节点,所以只要找到第一个叶子节点,然后依次根据兄弟节点指针找到兄弟节点,最后就完成了整个数据库的遍历。
而B树等需要前序遍历这种操作,在内存中倒是没有差别,但是如果是在磁盘中,因为父子节点往往不在一个页中,遍历就涉及到非常多的磁盘IO,效率很低。
-
-
索引
InnoDB使用的是聚簇索引表,即一棵以主键为索引、行数据存储在子节点上的B+树。如果没有主键,系统先找一个not null 且unique的列作为主键,如果没有,就会默认创建一个隐藏的主键作为索引。
其他的索引都是一个独立的结构,利用这个结构找到对应的主键,即通过其他索引找到数据需要先到对应的索引树找到主键,然后再到主键的索引树上找到数据。
如果where条件中的键值没有索引,会扫描全表。 -
锁
mysql中的锁有很多种:- 读写锁
读写锁是最常见的两种锁。读锁也叫共享锁,允许其他人再获得读锁,可以同时读,但是不能更改数据。写锁也叫排他锁,不允许其他人对数据进行读或者写。 - Record Lock
Record锁是一种索引锁,它加在索引上防止被多次引用 - Gap Lock
Gap锁的出现主要是用来解决幻读,锁住一个区间,没有拿到这个区间的Gap Lock,就不能向这个区间插入。 - NextKey Lock
NextKey锁其实就是Record Lock和Gap Lock的组合体,同时锁住一个范围和其中的记录 - 意向锁
意向所用来协调表锁和行锁。如果一个表有行排他锁存在,那么想对表添加排他锁,就一定要等这个行锁释放。但是想判断表中是否存在行锁只能遍历,消耗太大。为了解决这个问题,产生了意向锁。
当获取一个行锁时,会先拿到一个表的意向锁,这样只要判断一个表是否有意向锁,就可以判断是否可以添加表锁了。
- 读写锁
-
事务隔离级别
- read-uncommited
在这个级别下,读写不会加任何锁,所以即使事务没有提交,互相之间也能读到彼此的更改。因此可能读到对方更改一半的脏数据(脏读)。 - read-commited(RC)
在这个级别下,写的时候会加写锁,即在一个事务在更改一个数据的时候,其他事务无法获取这个数据,直到这个事务释放写锁之后。但是如果在那个事务更改之前读了一次,更改之后又读了一次,这两次的结果会不一致(不可重复读)。因此InnoDB采用了快照读解决这个问题,即在每行数据添加一个隐藏字段,最新更改的事务id,如果当前的事务id小于这个值, 就回去undo log中找到本事务对应的值,从而保证事务的一致性。
但是如果其他事务插入了一条数据,那么插入前这个事务是看不到那条数据的,但是插入之后,这个事务就可以select到那个数据。也就是说,同样的select语句,前后两次数据的会多或者少(幻读) - repeatable-read(RR)
在这个级别下,为了解决幻读,添加gap lock。即在select到的数据中间添加gap锁,其他想插入的事务必须要拿到这个gap锁才能插入,否则必须等待这个事务释放gap锁。
4.SERIALIZABLE
在RR的基础上,转换所有 SELECT 语句为SELECT ... LOCK IN SHARE MODE。即所有的读和写都要加锁。
- read-uncommited
-
死锁
造成死锁的原因都是因为加锁的顺序不同,具体原因可以分为业务和索引。-
业务导致的死锁
业务层加锁时顺序不同
这种情况比较常见,也比较容易解决,在代码中,两个线程在加锁的时候,加锁顺序不同,导致他们互相需要对方的锁。
-
索引导致的死锁
索引顺序不同导致死锁
这种情况就比较特殊,我们在业务层几乎感觉不到这种死锁,因为这个涉及到mysql对索引的操作。两个语句同时需要加锁,而且条件是在两个不同的索引。这样加锁顺序就取决于主键在两个索引的顺序了。如图中所示,两个索引的顺序相反,加锁的顺序也就相反,容易死锁。
我们需要注意的就是,如果操作一个有多个索引的数据表,尽量不要在多个索引上同时操作。
-
-
分析方法
- show engine innodb status
总结
- InnoDB是通过索引实现的行锁
所以如果select中条件是一个没有索引的列,那么会导致表锁,如果事务隔离级别是RR,那么事务结束之前都不会释放。 - 使用Gap锁
如果select一个不存在的数据,那么也会在对应的区间添加gap锁。
等于号的使用会影响范围,不好的话会锁住gap(next key) - 不使用索引
- 如果MySQL估计使用索引比全表扫描更慢,则不使用索引。例如,如果列key均匀分布在1和100之间,下面的查询使用索引就不是很好:select * from table_name where key>1 and key<90;
- 如果使用MEMORY/HEAP表,并且where条件中不使用“=”进行索引列,那么不会用到索引,head表只有在“=”的条件下才会使用索引
- 用or分隔开的条件,如果or前的条件中的列有索引,而后面的列没有索引,那么涉及到的索引都不会被用到,例如:select * from table_name where key1='a' or key2='b';如果在key1上有索引而在key2上没有索引,则该查询也不会走索引
- 复合索引,如果索引列不是复合索引的第一部分,则不使用索引(即不符合最左前缀),例如,复合索引为(key1,key2),则查询select * from table_name where key2='b';将不会使用索引
- 如果like是以‘%’开始的,则该列上的索引不会被使用。例如select * from table_name where key1 like '%a';该查询即使key1上存在索引,也不会被使用
- 如果列为字符串,则where条件中必须将字符常量值加引号,否则即使该列上存在索引,也不会被使用。例如,select * from table_name where key1=1;如果key1列保存的是字符串,即使key1上有索引,也不会被使用。
- 重复加锁
对不存在的记录加排他锁,都会加锁成功。即gap锁的x锁不互斥。
网友评论