MySQL面试题

索引

索引是什么,为什么要使用索引呢?

索引是帮助MySQL高效获取数据的数据结构。比如我们使用这样一条sql语句,

select * from user where age = 20,在无索引的情况下,我们查询数据可能需要全表一行一行的查询age为20的数据,但是有了索引之后,像innodb存储引擎的索引结构是改进后的B+树,通过索引能够比较快的定位到我们需要查询的数据。

常见的索引结构有哪些呢?

常见的索引结构有B+Tree索引,Hash索引,R-tree索引,Full-text索引

什么是B+树呢?它和二叉查找树有什么差异呢?

B+Tree和二叉查找树都是树形结构的索引,二叉树查找是每个节点至多有两个子树,这就会造成一个问题,在数据量比较大的情况下,二叉树的层级会很深,降低检索速度。同时,如果按照主键顺序插入数据,如从小到大插入(或者从大往小插入),二叉树会退化成单向链表,这样也会大大降低检索效率。

AVL树:是一个种自平衡的二叉查找树,它的特点是任何节点的左右子树高度差不超过1,因此它的查找,插入,删除的时间复杂度比较稳定,是O(n),能够解决二叉查找树退化成链表的问题。但是也未能在大数据的情况下,二叉树过深的问题。

红黑树:也是一种自平衡的二叉查找树,通过在插入和删除节点时进行颜色变换和旋转操作,使得树始终保持平衡,但是红黑树只是追求大致的平衡,因此在插入和删除元素时,只需少量的旋转和变色即可完成,提高了效率。但是和AVL树一样,未能解决大数据的情况下,二叉树过深的问题。

因此后面引入了多路平衡查找树,也就是B-Tree,与前面提到的树相比,B树每个节点可以有多个分支。分支的个数由阶数决定,比如5阶的B-Tree,那么该B树每个节点可以存储4个key,5个指针,即该B树有5个分支。同时B树的非叶子节点和叶子节点都存放数据。

而B+树对B树进行了改进,主要的差异有这么几点,

B+树与B树差异

B+树所有的数据都会出现在叶子节点,非叶子节点仅仅起到索引的作用,而B树叶子节点和非叶子节点都会存储数据,因此对于B+树的检索,最终都会到叶子节点上进行查询,而B树有可能未到叶子节点就检索结束了。因此B+树的检索更加稳定。同时由于B+树的节点只存储键,而不存放数据,因此B+树每一个节点能存放更多的key,因此B+树深度会更低,磁盘IO也会减少。

(因为一次磁盘IO会读取一页的数据,即一个节点,B+树一页是16K,一个key是8个字节,因此一页能存放1000多个key)

B+树叶子节点形成一个单向链表,而B树的叶子节点是独立的。因此进行范围查询时如select * from user where age > 20,B+树需要检索了范围查询的下限,然后遍历叶子节点;而B树进行范围查询,需要先找到下限节点,然后中序遍历B树。

MySQL对B+树进行改进

当使用范围查询select * from user where age < 20时候,原生B+树的叶子节点只有向右的指针,同时B+树底层排序默认为从小到大,这时候在进行age < 20的操作时,唯有从Root节点逐个搜索比较满足<20这个数值的数据才能遍历到所有数据,这非常浪费计算机的时间和性能。

而改进后的B+树,在进行范围搜索时双向指针可以完美地解决逆向搜索带来的时间和性能浪费的问题,当我们需要搜索一个满足age < 20时,只需要找到该上限的节点,然后通过双向循环链表向前搜索便可以得到所有数据。同时在对10 < age < 20时,只需要定向到一个满足该条件的任何一个值,然后通过链表双向循环搜索便可以极快地搜索出需要的值。

B+树缺陷:

B+树最大的性能问题是会产生大量的随机IO,随着新数据的插入,叶子节点会慢慢分裂,逻辑上连续的叶子节点在物理上往往不连续,甚至分离的很远,但做范围查询时,会产生大量读随机IO。

对于大量的随机写也一样,举一个插入key跨度很大的例子,如7->1000->3->2000 ... 新插入的数据存储在磁盘上相隔很远,会产生大量的随机写IO。

Hash索引

hash索引是利用hash算法,将键值计算成hash值,映射到对应的槽位上,然后存储在hash表中。

对于存在hash冲突的槽位,使用拉链法来解决。

hash索引的查询效率高,通常只需要一次检索即可;但是只能用来对等比较,如=,in,而不支持范围查询,如between,>,<;同时无法利用索引完成排序操作。

因此Inndb存储引擎使用B+tree索引结构,

A. 相对于二叉树,层级更少,搜索效率高;

B. 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储 的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;

C. 相对Hash索引,B+tree支持范围匹配及排序操作;

索引分类

按照应用来分:索引可以分为主键索引,唯一索引,常规索引,全文索引

主键索引:是指针对表中的主键创建的索引,只能有一个,

唯一索引:避免表中的某列字段重复,可以有多个,

常规索引:仅仅加速查询,

全文索引:全文索引是指查找文本中的关键字,

按照底层存储方式来分:聚簇索引和非聚簇索引(二级索引)

聚簇索引:将数据和索引放在了一起,索引结构的叶子节点保存了数据(唯一),

非聚簇索引:数据和索引分开存储,索引结构的叶子节点仅仅存放的是主键(不唯一),

聚簇索引的选取规则:

如果表中存在主键,那么主键索引就是聚簇索引;如果不存在主键,那么使用第一个唯一索引作为聚簇索引;如果表没有主键也没有唯一索引,那么Innodb会自动生成一个rowid作为隐藏的聚簇索引。

聚簇索引和非聚簇索引有什么区别呢?

由于聚簇索引下的叶子节点既存放了索引又存放了这一行的数据,因此聚簇索引只需要一次查询就能查询到相应的数据;而由于非聚簇索引,叶子节点下仅存储索引和主键,因此还需要回表查询,既先查到对应的主键,然后通过主键查询到相应的数据。

聚簇索引的优缺点是什么呢?

由于叶子节点上就是相应的数据,因此查询速度比较快,不需要回表查询;而缺点在于依赖有序的数据,由于B+树是多路平衡查找树,如果索引的数据不是有序的,那么在插入时候就要排序;另外,索引列的数据被修改时,更新的代价比较大。

非聚簇索引的优缺点是什么呢?

优点:由于非聚簇索引叶子节点不存放数据,因此更新代价要小;缺陷在于依赖有序数据,同时会又回表查询。

非聚簇索引一定会回表查询吗?

不一定,如果要查询的数据仅仅包含该索引,就不需要回表查询。

比如我们为table表的name字段创建了一个普通索引,同时我们只查询name这个字段,select name from table where name = "zhangsan",因为索引本身就是name,查到了name就直接返回,无需回表查询。

覆盖索引:是指一个索引覆盖了所需查询的字段,那么根据该索引就能够直接查询到相应的相互据,在这种情况下无需回表查询。

联合索引

使用表的多个字段创建的索引,

联合索引使用原则,最左前缀匹配法则,即在使用联合索引时,MySQL会根据联合索引的字段顺序,从左到右的到查询条件中进行匹配,直到所有索引字段匹配完成,如果中间跳跃了某一列,该列及后边的索引都会失效;

比如我们为profession,age,status创建了联合索引,

这种情况下,涉及到的三个索引字段都在,那么该索引完全生效。

select * from tb_user where profession = '软件工程' and age = 31 and status = '0';

 而在这种情况下,只使用到了profession和age,那么status索引失效

select * from tb_user where profession = '软件工程' and age = 31;

而在这种情况下,只使用到了profession索引,那么age,status索引失效

select * from tb_user where profession = '软件工程';

而在下面的情况下,没有使用到profession索引,不满足最左匹配原则, 因此索引会完全失效。

select * from tb_user where age = 31 and status = '0';

另一个原则是,当出现范围查询(>,<),范围查询右侧的列索引失效。

如下面字段,status索引将失效。

select * from tb_user where profession = '软件工程' and age > 30 and status
= '0';

索引失效的情况 

1.在索引列上进行运算操作,索引将失效

如我们为phone字段创建了索引,但是我们在使用phone进行查询时候,给phone进行了函数运算,那么索引失效。

select * from tb_user where substring(phone,10,2) = '15';

2.字符串不加引号使用时,索引失效,隐式转换。

隐式转换是指,不以数字开头的字符串转换为0,以数字开头的字符串会进行截取,从第一个字符截取到非数字内容。然后转换成数字,因此有可能不一样的字段经过隐式转换后,变成相同的数字,这样索引就会失效。如表中有这样的数据

3.使用头部进行模糊查询时,索引将失效,

如%在字段前索,索引将会失效

select * from tb_user where profession like '%工程';
select * from tb_user where profession like '%工%';

4.or连接条件,用or分割开的条件,如果or前后两个条件如果有一个没有索引,那么索引就会失效

5.数据分布的影响,如果MySQL评估中,使用索引比全表扫描更慢,则不会使用索引。因为索引是用来索引少量数据,如果通过索引返回大量的数据,则性能可能比全表扫描还慢,这个时候索引就会失效。

6.创建了联合索引,但是查询条件没有遵循最左前缀匹配原则。

索引设计的原则

1.针对数据量比较大,且查询比较频繁的字段建立索引

2.常用于查询条件,排序,分组操作的字段建立索引

3.选择区分度高的字段作为索引,即该字段重复值比较少,尽量建立唯一索引

4.尽量使用联合索引,减少单列索引,这样覆盖索引,避免回表查询

5.要控制索引数量,索引并不是越多越好

MySQL三大日志

 redo log

redo log是重做日志,记录的是事务提交时数据页的修改。该日志文件包括两个部分,一个是重做日志缓冲(redo log buffer),该日志文件存储在内存中,一个是重做日志文件(redo log file),存储在硬盘中。事务提交后会把所有的修改信息存到日志文件中,用于在刷新脏页到磁盘,发生错误时候,进行数据恢复。

为什么需要redo log呢?

在Innodb存储引擎中,主要的内存区域是缓冲池,在缓冲池中缓存了很多的数据页。当我们进行增删改时,会先操作缓冲池中的数据,如果缓冲池没有相应的数据,那么会从磁盘中读取存放在缓冲池中,然后进行修改。修改之后的数据页称为脏页,因为和磁盘中的数据不一样。而脏页会在一定的时机,通过后台线程刷新到磁盘中,从而保证缓冲区与磁盘数据一致。但是缓冲区的脏页数据刷新到磁盘并不是实时的。假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,但是数据并没有持久化。

有了redo log,在对缓冲区的数据进行增删改之后,会记录数据页的变化,记录在redo log buffer中,事务提交后,redo log buffer中的数据会刷新到redo log磁盘文件中。因此,即使刷新缓冲区的脏页到磁盘发生错误,也可以借助redo log进行数据恢复,从而保证事务的持久性。让MySQL具备崩溃后,恢复数据库的能力。

为什么不直接将buffer pool中的脏页刷新到磁盘呢?而是将redo log刷新到磁盘呢?

因为在业务操作中,操作数据都是随机读写磁盘,而不是顺序读写磁盘。而redo log刷新到磁盘,是基于顺序写的。其效率要远高于随机写。同时redo log占用的空间比较小。

redo log的刷盘策略是什么呢?

Innodb有一个后台线程,会每隔1s将redo log进行刷盘。

redo log buffer空间达到设定的一半时候,也会刷盘。

主要可以通过刷盘策略innodb_flush_log_at_trx_commit这个参数来控制,

当刷盘策略参数为0时,每次事务提交都不刷盘。这种性能最高,但是最不安全,如果MySQL宕机可能会丢失最近1s内的事务。

当刷盘策略为1时,表示每次事务提交都进行刷盘。这种性能最低,但是最安全。因为只要事务提交,redo log就一定刷新到磁盘中。

当刷盘策略为2时,表示每次事务提交都redo log写入page cache(文件系统缓存),然后调用fsync刷盘。

undo log

回滚日志。记录数据被修改前的信息。主要有两个作用:事务回滚和MVCC。执行事务前,MySQL会把更新前的数据都记录到undo log里面(如执行的是delete 则记录insert)。当执行roll back时,则MySQL利用undo log进行回滚。

undo log 格式:1.roll_pointer将undo log形成一个版本链  2.trx_id 记录被哪个事务修改。

另外一个作用是配合read view以及MySQL隐藏字段实现MVCC。

undo log在事务提交后会立即销毁吗?

undo log在事务执行时产生,事务提交后,并不会立即删除undo log,因为undo log还会用于MVCC.

undo log如何实现MVCC呢?

binlog

binlog是逻辑日志,记录每一条原始SQL语句,事务提交后,写入binlog文件。主要用于数据备份、主备、主主、主从。

binlog的记录格式

binlog有三种格式,通过binlog_fomat参数指定,如果设置为statement,那么记录的是SQL原文,但是如果执行了类似于update table set update_time= now() where id = 1,同步数据时候,执行这个SQL,那么该时间就会与原库时间不一致。因此我们需要设置成row,记录的内容不再是简单的SQL语句,还包含具体的操作数据。但是由于这种格式会占据大量空间,因此就有了第三种方式,设置成mixed时,记录的是两者的混合。MySQL会判断该语句是否会引起数据不一致,如果会引起数据不一致,那么就用row格式,否则用statement格式。

由于redo log和binlog在事务提交后,会持久化到磁盘上,因为这是两个动作,有可能一个成功,一个失败,这样就导致了不一致。因此使用两阶段提交的方法保证一致性。

prepare阶段:将 XID(内部 XA 事务的 ID) 写入到 redo log,同时将 redo log 对应的事务状态设置为 prepare,然后将 redo log 持久化到磁盘;

commit阶段:把 XID 写入到 binlog,然后将 binlog 持久化到磁盘,将 redo log 状态设置为 commit,此时该状态并不需要持久化到磁盘,只需要 write 到文件系统的 page cache 中就够了,因为只要 binlog 写磁盘成功,就算 redo log 的状态还是 prepare 也没有关系,一样会被认为事务已经执行成功;

两种情况下崩溃:1.如果redo log 已经写入磁盘, binlog 还没写入磁盘时候崩溃(A),redo log 和 binlog 都已经写入磁盘,还没写入 commit 标识(B)

MySQL 重启后会按顺序扫描 redo log 文件,用redo log中的XID去binlog查看是否存在此XID:如果 binlog 中没有当前内部 XA 事务的 XID,说明 redolog 完成刷盘,但是 binlog 还没有刷盘,则回滚事务

如果 binlog 中有当前内部 XA 事务的 XID,说明 redolog 和 binlog 都已经完成了刷盘,则提交事务

参考资料:MySQL三大日志_#HashMap#的博客-优快云博客

MVCC是什么呢?

MVCC是多版本并发控制,含义是维护一个数据的多个版本,使得读写没有冲突。MVCC具体实现依赖于数据库3个隐藏字段,undo log日志,readView(读视图).

这3个隐藏字段是指,DB_TRX_ID,最近修改事务ID,DB_ROLL_PTR,回滚指针,指向这个记录的上一个版本。DB_ROW_ID,隐藏主键,如果表没有主键,则会生成该隐藏字段。

版本链:不同事务或相同事务对同一条记录进行修改,会导致该记录的undo log,然后通过DB_ROLL_PTR进行连接上一个事务的DB_TRX_ID,因此会生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。

ReadView记录系统当前活跃的事务id(即未提交的事务id),快照读SQL执行时依据这个ReadView的匹配规则,上undo log版本链从上到下进行匹配。匹配到满足规则的那么就读取该版本链记录的数据。

ReadView的匹配规则是什么呢?

ReadView包含了这几个字段,m_ids,当前活跃事务ID集合;min_trx_id,最小活跃事务ID;

max_trx_id,预分配事务ID,即当前最大事务ID+1;creator_trx_id,ReadView创建者的事务ID;

访问规则是,比如当前undo log版本链中对应的事务ID是trx_id,如果trx_id和creator_trx_id相等,则意味着这个数据是当前事务更改的,则可以访问;如果trx_id<min_trx_id,说明这个版本的事务已经提交了,则也可以访问;如果trx_id>max_trx_id,说明该事务时在ReadView生成后才开启的,因此不可以访问;如果trx_id在最小事务ID和最大事务ID之间,并且不在活跃事务集合中,说明数据已经提交了,则可以访问。

RR与RC隔离级别下ReadView的差异

在RR隔离级别下,仅在事务第一次执行快照都的时候生成ReadView,后续复用该ReadView,因此在一个事务中执行两次相同的select语句,查询的结果是一样的,因此实现了可重复读。

而在RC隔离级别下,每次执行快照都会生成ReadView,因此在不同的ReadView匹配规则下,有可能前后执行的快照读,读取的数据不一致,从而导致不可重复读的问题。

MVCC可以防止幻读吗?

MVCC可以防止部分幻读,在RR隔离级别下,在事务第一次执行快照读时生成ReadView,后续快照读复用该ReadView。在ReadView生成之后的其他事务做的更新,插入,删除的记录对当前事务不可见。因此解决了快照读的情况下的幻读问题。但是在当前读的情况下,如执行select...for update/lock in share mode、insert、update、delete,由于读取的是最新的数据,因此如果有其他事务插入数据,并且刚好在当前事务的查询范围内,就会出现幻读问题。因此在快照读的情况下,需要配合临间锁,锁住当前范围的记录,防止其他事务插入数据,这样就可以避免在快照读的情况下出现幻读问题。

幻读和不可重复读区别

幻读在于事务中多次查询一个范围的数据,发现新增了一些数据,对应的是insert的;而不可重复读在于事务中多次查询某一行数据,发现该行数据消失了或者修改了,对应的是update或delete。在当前读的情况下,只能使用悲观锁来解决这个问题。之所以将他们分成幻读和不可重复读,一个原因在于解决它们的方案不同;幻读会使用临间锁来锁住该范围内的数据,因此其他事务无法同时在这个范围内插入数据;而不可重复读使用行锁,锁住该行数据,这样其他事务无法同时对这行数据进行修改或删除

事务

事务四大特性

原子性(Atomicity),事务时最小的执行单位,不允许分割。

一致性(Consistency),事务执行前后,数据保持一致

隔离性(Isolation),多个事务并发访问数据库,各个事务之间互不干扰

持久性(Durability),一个事务提交后,对数据库中的数据改变是持久的。

并发事务会有什么问题

脏读,是指读取了尚未提交的数据,导致读取的数据可能与数据库中的数据不一致。如表中有一条记录money为100,事务A将money减去10,为90,但是事务还未提交时,事务B读取了该记录money为90,但是此时事务A发生了回滚,因此数据库中的money为100,这就导致了事务B读取的记录与数据库中的记录不一致。这就是脏读。

不可重复读,是指在一个事务中,多次读取表中的记录,发现前后读取的数据不一样。如表中有一个记录money=100,事务A读取该记录money=100,但是事务B修改money=90,此时事务A再次读取该记录发现money=90,前后读取的记录不一致,这个就是不可重复读。

幻读,是指在一个事务中,多次读取表中的记录,发现读取的数据记录变多了或变少了。如表A中有两条记录money=90,money=100,事务A读取[90,100]范围的记录,读取了这两条记录;事务B插入了一条记录money=95,事务A再次读取[90,100]范围内的记录,发现变成了3条,读取的数据记录变多了,这就是幻读问题。

事务的隔离级别有哪些呢?

事务有四种隔离级别,Read uncommitted;Read committed;Repeatable Read;Seriable;

Read uncommitted是读取未提交的数据,因此可能会造成脏读,不可重复读,幻读问题;

Read committed是读取已提交的数据,可以避免脏读,但是依然会造成不可重复读和幻读问题;

Repeatable Read是可重复读,可以避免脏读,不可重复读,以及可以避免在快照读的情况下的幻读问题,但是在当前读的情况下依然会存在幻读问题;

Seriable是最高级的隔离级别,可以避免脏读,不可重复读以及幻读问题。

查看事务隔离级别的指令:@@TRANSACTION_ISOLATION;

设置事务隔离级别的指令:SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }

SQL优化

插入数据优化

当我们需要往数据库插入多条数据时候,可以采取这几种方式优化

批量插入数据

Insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');

按照主键顺序插入;使用load命令大批量加载数据

主键优化

降低主键长度;按照主键顺序插入;尽量不要使用UUID做主键或其他自然主键,如身份证号,这样可以减少页分裂和页合并;在业务操作时候,避免修改主键;

order by优化

根据排序的字段建立合适的索引;多字段排序,要遵循最左前缀法则;尽量使用覆盖索引;多字段排序,一个升序一个降序,此时应该注意联合索引创建时字段的排序规则;

group by优化

分组操作也可以通过索引来提高效率;同时也要遵循最左前缀法则;

limit优化

覆盖索引加子查询的方式

比如我们要要查询

select * from table limit 2000000,10;

这种方式的问题是它会查询table表从第一条到200000,找到了200000数据后,继续读10条数据,然后只返回200001到200010范围的数据,前面的200000全部丢弃,这样就造成了极大的浪费。

我们可以通过子查询的方式,先根据主键id查询[2000001,2000010]的数据,因为子查询只用到了索引列id,所以速度很快,然后由于主查询用到了准确的索引值,所以速度也会很快,从而提高了分页查询的性能。

或者使用,直接从第2000000直接开始查询,也能提高性能。

select * from table where id > 2000000 limit 10;

select * from tb_sku t , (select id from tb_sku order by id limit 2000000,10) a where t.id = a.id;

count优化

count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加,最后返回累计值。

性能从高到低排序:count(*)≈count(1)>count(id)>count(字段),

因为count(字段)中,Innodb引擎会把每一行的该字段取出来,然后服务层判断是否为null,而count(主键)虽然也需要取出主键,但是不用判断是否为null;而count(数字)会遍历整张表,但是不取值,服务层对Innodb返回的每一行,放一个数字“1”,然后累加;count(*)与count(1)类似,不会取值,按行累加。

update优化

进行update更新时,如果条件字段用到了索引,则只会使用行锁,锁住更新的记录,而如果条件字段没有用到索引,那么行锁将升级为表锁,降低性能。

如table表有两个字段id和name,id是主键,建立了索引,那么是普通字段,没有建立索引。

下面一条更新语句执行时候就只会锁住id为1的记录。

update table set name = 'moda' where id = 1 ;

而下面的一条更新语句由于条件是name字段,没有索引,因此执行时候会锁住整张表。

update course set name = 'xiaoxiangyeyu' where name = 'moda' ;

SQL性能分析

SQL执行频率

使用如下命令,可以知道当前数据库增删改查的次数,因此可以判断该数据库是以增删改为主,还是以查询为主。如果是以查询为主就可以考虑对索引进行优化,我们可以根据慢日志查询来针对性的进行索引优化。

SHOW GLOBAL STATUS LIKE 'Com_______';

慢查询日志

要想使用慢日志查询,首先需要在配置文件中进行配置,将slow_query_log=1,开启慢日志查询,同时设置long_query_time=2,SQL语句大于2秒的,会被视作慢查询。然后我们执行SQL语句后,在慢查询日志中就会记录执行时间大于2秒的sql语句,因此进行针对性的优化。

profile详情

通过profile进行判断,首先需要开启profile,开启之后,SQL语句会被记录分析在sql执行过程中的各个步骤的执行时间。

-- 查看每一条SQL的耗时基本情况 show profiles;

-- 查看指定query_id的SQL语句各个阶段的耗时情况 show profile for query query_id;

-- 查看指定query_id的SQL语句CPU的使用情况 show profile cpu for query query_id;

explain关键字

使用explain关键字,可以知道MySQL如何执行select语句信息,包括select执行过程中表如何连接和连接的顺序,以及是否用到索引等

Explain中常见的字段包括select_type,取值有simple(简单表,没有使用子查询或表连接),PRIMARY(主查询,即外层的查询),type表示连接的类型,性能从好到坏为NULL,system,const等,possible_key,该表可能会用到的索引,key,该select语句实际用到的索引,为null则表示没有用到索引,key_len,索引使用的字节数,为索引字段最大可能长度,在不影响精确性的前提下,长度越短越好,filtered,返回结果的行数占需要读取行数的百分比,该值越大越好。

使用语法

EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件 ;

幻读与不可重复读比较类似

[1]林荣杭,刘小英.MySQL索引改进的B+树的研究[J].电脑知识与技术,2022,18(16):12-13+18.DOI:10.14004/j.cnki.ckt.2022.1080

[2]黑马程序员

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值