深入浅析Mysql联合索引最左匹配原则

深入浅析Mysql联合索引最左匹配原则,第1张

之前在网上看到过很多关于mysql联合索引最左前缀匹配的文章,自以为就了解了其原理,最近面试时和面试官交流,发现遗漏了些东西,这里自己整理一下这方面的内容。

最左前缀匹配原则

在mysql建立联合索引时会遵循最左前缀匹配的原则,即最左优先,在检索数据时从联合索引的最左边开始匹配,示例:

对列col1、列col2和列col3建一个联合索引

KEY test_col1_col2_col3 on test(col1,col2,col3)

联合索引 test_col1_col2_col3 实际建立了 (col1)、(col1,col2)、(col,col2,col3) 三个索引。

SELECT * FROM test WHERE col1=“1” AND clo2=“2” AND clo4=“4”

上面这个查询语句执行时会依照最左前缀匹配原则,检索时会使用索引(col1,col2)进行数据匹配。

注意

索引的字段可以是任意顺序的,如:

SELECT * FROM test WHERE col1=“1” AND clo2=“2”

SELECT * FROM test WHERE col2=“2” AND clo1=“1”

这两个查询语句都会用到索引(col1,col2),mysql创建联合索引的规则是首先会对联合合索引的最左边的,也就是第一个字段col1的数据进行排序,在第一个字段的排序基础上,然后再对后面第二个字段col2进行排序。其实就相当于实现了类似 order by col1 col2这样一种排序规则。

有人会疑惑第二个查询语句不符合最左前缀匹配:首先可以肯定是两个查询语句都保函索引(col1,col2)中的col1、col2两个字段,只是顺序不一样,查询条件一样,最后所查询的结果肯定是一样的。既然结果是一样的,到底以何种顺序的查询方式最好呢?此时我们可以借助mysql查询优化器explain,explain会纠正sql语句该以什么样的顺序执行效率最高,最后才生成真正的执行计划。

减少开销 。建一个联合索引(col1,col2,col3),实际相当于建了(col1),(col1,col2),(col1,col2,col3)三个索引。每多一个索引,都会增加写 *** 作的开销和磁盘空间的开销。对于大量数据的表,使用联合索引会大大的减少开销!

覆盖索引 。对联合索引(col1,col2,col3),如果有如下的sql: select col1,col2,col3 from test where col1=1 and col2=2。那么MySQL可以直接通过遍历索引取得数据,而无需回表,这减少了很多的随机io *** 作。减少io *** 作,特别的随机io其实是dba主要的优化策略。所以,在真正的实际应用中,覆盖索引是主要的提升性能的优化手段之一。

效率高 。索引列越多,通过索引筛选出的数据越少。有1000W条数据的表,有如下sql:select from table where col1=1 and col2=2 and col3=3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W10%=100w条数据,然后再回表从100w条数据中找到符合col2=2 and col3= 3的数据,然后再排序,再分页;如果是联合索引,通过索引筛选出1000w10% 10% *10%=1w,效率提升可想而知!

引申

对于联合索引(col1,col2,col3),查询语句 SELECT * FROM test WHERE col2=2是否能够触发索引?

大多数人都会说NO,实际上却是YES。

原因:

EXPLAIN SELECT * FROM test WHERE col2=2

EXPLAIN SELECT * FROM test WHERE col1=1

观察上述两个explain结果中的type字段。查询中分别是:

index: 这种类型表示mysql会对整个该索引进行扫描。要想用到这种类型的索引,对这个索引并无特别要求,只要是索引,或者某个联合索引的一部分,mysql都可能会采用index类型的方式扫描。但是呢,缺点是效率不高,mysql会从索引中的第一个数据一个个的查找到最后一个数据,直到找到符合判断条件的某个索引。所以,上述语句会触发索引。

ref: 这种类型表示mysql会根据特定的算法快速查找到某个符合条件的索引,而不是会对索引中每一个数据都进行一一的扫描判断,也就是所谓你平常理解的使用索引查询会更快的取出数据。而要想实现这种查找,索引却是有要求的,要实现这种能快速查找的算法,索引就要满足特定的数据结构。简单说,也就是索引字段的数据必须是有序的,才能实现这种类型的查找,才能利用到索引。

以上所述是我给大家介绍的Mysql联合索引最左匹配原则,希望对大家有所帮助,如果大家有任何疑问请给我留言,我会及时回复大家的。

《 两个月拿到N个offer,看看我是如何做到的 》

《 面试总结:2019年最全面试题资料学习大全—(含答案) 》

《 淘宝面试回来,想对程序员们谈谈 》

《 看过太多大厂面试题,其实考的无非是这 3 点能力 》

MySQL的异常处理分析如下:

标准格式

DECLARE

handler_type

HANDLER

FOR

condition_value[,...]

statement

handler_type:

CONTINUE

|

EXIT

|

UNDO

--这个暂时不支持

condition_value:

SQLSTATE

[VALUE]

sqlstate_value

|

condition_name

|

SQLWARNING

|

NOT

FOUND

|

SQLEXCEPTION

|

mysql_error_code

condition_value细节

1、常用MYSQL

ERROR

CODE

列表

http://dev.mysql.com/doc/refman/5.0/en/error-messages-server.html

更多错误列表见MySQL安装路径下

比如我的/usr/local/mysql/share/mysql/errmsg.txt

说明一下:SQLSTATE

[VALUE]

sqlstate_value这种格式是专门为ANSI

SQL

ODBC以及其他的标准.

并不是所有的MySQL

ERROR

CODE

都映射到SQLSTATE。

2、如果你不想插ERROR

CODE的话,就用速记条件来代替

SQLWARNING

代表所有以01开头的错误代码

NOT

FOUND

代表所有以02开头的错误代码,当然也可以代表一个游标到达数据集的末尾。

SQLEXCEPTION

代表除了SQLWARNING和NOT

FOUND

的所有错误代码

3、我们现在就用手册上的例子

CREATE

TABLE

t

(s1

int,primary

key

(s1))

mysql>

use

t_girl

Database

changed

mysql>

CREATE

TABLE

t

(s1

int,primary

key

(s1))

Query

OK,

0

rows

affected

(0.00

sec)

mysql>

mysql>

mysql>

DELIMITER

||

mysql>

CREATE

PROCEDURE

handlerdemo

()

->

BEGIN

->

DECLARE

EXIT

HANDLER

FOR

SQLSTATE

'23000'

BEGIN

END

--

遇到重复键值就退出

->

SET

@x

=

1

->

INSERT

INTO

t

VALUES

(1)

->

SET

@x

=

2

->

INSERT

INTO

t

VALUES

(1)

->

SET

@x

=

3

->

END||

Query

OK,

0

rows

affected

(0.00

sec)

mysql>

DELIMITER

mysql>

call

handlerdemo()

Query

OK,

0

rows

affected

(0.00

sec)

mysql>

select

@x

+------+

|

@x

|

+------+

|

2

|

+------+

1

row

in

set

(0.00

sec)

mysql>

call

handlerdemo()

Query

OK,

0

rows

affected

(0.00

sec)

mysql>

select

@x

+------+

|

@x

|

+------+

|

1

|

+------+

1

row

in

set

(0.00

sec)

mysql>

现在来看一下遇到错误继续的情况

mysql>

truncate

table

t

Query

OK,

0

rows

affected

(0.01

sec)

mysql>

DELIMITER

$$

mysql>

DROP

PROCEDURE

IF

EXISTS

`t_girl`.`handlerdemo`$$

Query

OK,

0

rows

affected

(0.00

sec)

mysql>

CREATE

DEFINER=`root`@`localhost`

PROCEDURE

`handlerdemo`()

->

BEGIN

->

DECLARE

CONTINUE

HANDLER

FOR

SQLSTATE

'23000'

BEGIN

END

->

SET

@x

=

1

->

INSERT

INTO

t

VALUES

(1)

->

SET

@x

=

2

->

INSERT

INTO

t

VALUES

(1)

->

SET

@x

=

3

->

END$$

Query

OK,

0

rows

affected

(0.01

sec)

mysql>

DELIMITER

mysql>

call

handlerdemo()

Query

OK,

0

rows

affected

(0.00

sec)

mysql>

select

@x

+------+

|

@x

|

+------+

|

3

|

+------+

1

row

in

set

(0.00

sec)

mysql>

call

handlerdemo()

Query

OK,

0

rows

affected

(0.00

sec)

mysql>

select

@x

+------+

|

@x

|

+------+

|

3

|

+------+

1

row

in

set

(0.00

sec)

mysql>

可以看到,始终执行到最后。

当然,上面的SQLSTATE

'23000'可以替换为1062

我们来看一下警告。

mysql>

alter

table

t

add

s2

int

not

null

Query

OK,

0

rows

affected

(0.01

sec)

Records:

0

Duplicates:

0

Warnings:

0

此列没有默认值,插入的时候会出现警告或者1364错误提示。

mysql>

DELIMITER

$$

mysql>

DROP

PROCEDURE

IF

EXISTS

`t_girl`.`handlerdemo`$$

Query

OK,

0

rows

affected,

1

warning

(0.00

sec)

mysql>

CREATE

DEFINER=`root`@`localhost`

PROCEDURE

`handlerdemo`()

->

BEGIN

->

DECLARE

CONTINUE

HANDLER

FOR

1062

BEGIN

END

->

DECLARE

CONTINUE

HANDLER

FOR

SQLWARNING

->

BEGIN

->

update

t

set

s2

=

2

->

END

->

DECLARE

CONTINUE

HANDLER

FOR

1364

->

BEGIN

->

INSERT

INTO

t(s1,s2)

VALUES

(1,3)

->

END

->

SET

@x

=

1

->

INSERT

INTO

t(s1)

VALUES

(1)

->

SET

@x

=

2

->

INSERT

INTO

t(s1)

VALUES

(1)

->

SET

@x

=

3

->

END$$

Query

OK,

0

rows

affected

(0.00

sec)

mysql>

DELIMITER

mysql>

call

handlerdemo()

Query

OK,

0

rows

affected

(0.00

sec)

mysql>

select

*

from

t

+----+----+

|

s1

|

s2

|

+----+----+

|

1

|

3

|

+----+----+

1

row

in

set

(0.00

sec)

遇到错误的时候插入的新记录。

mysql>

select

@x

+------+

|

@x

|

+------+

|

3

|

+------+

1

row

in

set

(0.00

sec)

mysql分库分表一般有如下场景

其中1,2相对较容易实现,本文重点讲讲水平拆表和水平拆库,以及基于mybatis插件方式实现水平拆分方案落地。

在 《聊一聊扩展字段设计》 一文中有讲解到基于KV水平存储扩展字段方案,这就是非常典型的可以水平分表的场景。主表和kv表是一对N关系,随着主表数据量增长,KV表最大N倍线性增长。

这里我们以分KV表水平拆分为场景

对于kv扩展字段查询,只会根据id + key 或者 id 为条件的方式查询,所以这里我们可以按照id 分片即可

分512张表(实际场景具体分多少表还得根据字段增加的频次而定)

分表后表名为kv_000 ~ kv_511

id % 512 = 1 .... 分到 kv_001,

id % 512 = 2 .... 分到 kv_002

依次类推!

水平分表相对比较容易,后面会讲到基于mybatis插件实现方案

场景:以下我们基于博客文章表分库场景来分析

目标:

表结构如下(节选部分字段):

按照user_id sharding

假如分1024个库,按照user_id % 1024 hash

user_id % 1024 = 1 分到db_001库

user_id % 1024 = 2 分到db_002库

依次类推

目前是2个节点,假如后期达到瓶颈,我们可以增加至4个节点

最多可以增加只1024个节点,性能线性增长

对于水平分表/分库后,非shardingKey查询首先得考虑到

基于mybatis分库分表,一般常用的一种是基于spring AOP方式, 另外一种基于mybatis插件。其实两种方式思路差不多。

为了比较直观解决这个问题,我分别在Executor 和StatementHandler阶段2个拦截器

实现动态数据源获取接口

测试结果如下

由此可知,我们需要在Executor阶段 切换数据源

对于分库:

原始sql:

目标sql:

其中定义了三个注解

@useMaster 是否强制读主

@shardingBy 分片标识

@DB 定义逻辑表名 库名以及分片策略

1)编写entity

Insert

select

以上顺利实现mysql分库,同样的道理实现同时分库分表也很容易实现。

此插件具体实现方案已开源: https://github.com/bytearch/mybatis-sharding

目录如下:

mysql分库分表,首先得找到瓶颈在哪里(IO or CPU),是分库还是分表,分多少?不能为了分库分表而拆分。

原则上是尽量先垂直拆分 后 水平拆分。

以上基于mybatis插件分库分表是一种实现思路,还有很多不完善的地方,

例如:


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/7463918.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-06
下一篇 2023-04-06

发表评论

登录后才能评论

评论列表(0条)

保存