字符串的公共前缀对Mysql B+树查询影响回溯分析

年前项目组接微信公众号。

上线之后,跟微信相关的用cid列的查询会话的SQL变慢了几十倍!思考这个问题思考了非常久。从出现以来一直是我心头的一个结。cid这一列是建了索引的,普通的cid列更新都没问题,为何仅仅有微信的有问题?同样的前缀又是怎样影响索引的?

分析过程

1.explain下微信cid的查询。微信的cid会以mid-qqwanggou001为前缀插入数据

explain
select *
from analysis_sessions
where cid = "mid-qqwanggou001-b99359d9054171901c0"

分析结果例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" width="867" height="100" />

从explain分析能够看出。这个查询使用了索引,可是innodb觉得有165万行数据须要给mysqlserver筛选(也就是用where条件过滤)。

假设这些庞大的数据在内存,遍历一遍花不了多少时间。可是极有可能,这些数据是在磁盘上的。这么多的数据从磁盘读取然后加载内存。大量磁盘IO必定是十分的耗时的。

相比内存的电子运动。磁盘机械臂的物理运动要慢好几个数量级。

2.分析普通cid的查询

取数据进行explain。cid = "sid-a2f9047ddf528d837e5f60843c83aae9"。这个数据是不带公共前缀的。

   

explain
select *
from analysis_sessions
where cid = "sid-a2f9047ddf528d837e5f60843c83aae9"

分析结果例如以下:

同样的列,同样的索引。这次存储引擎向mysqlserver仅仅返回了一行数据。也就是说innodb仅仅须要读取一个二级索引的叶子节点。

相对于上面那个sql的IO,压力显然小非常多。

初步分析结论:带有长前缀的cid查询。innodb存储引擎会向mysql上端server返回百万级别的数据。

这仅仅是现象,我还是想问,同样的表,同样的列,同样的索引结构(B+树索引)。同样的查询,仅仅不同的数据。结果为何有差么大的区别?

近一步分析

纠结这个问题非常久了,直到前天晚上散步时候。无意的会想到了 explain结果的key_len这一列。这一列我从来不看,觉得没用。可是27与cid这一列50个varchar的定义格格不入。27明显小于50,首先能够肯定,这个索引用的是前缀索引,说白了,截取了字符串的前面一部分作为索引数据。analysis_session表用的gbk编码。也就是说,索引须要2个字节表示一个varchar。解释一下key_len

27 = 2 * 12 + 2 + 1

27位的索引,仅仅索引了前面12个字符。中间的2存储长度。后面的一个字节存储Null信息,由于这一列是同意Null的。

终于结论:问题到这已经非常明了了,微信cid的前缀是17个字符的,大于前缀索引的12个字符,也就是说。全部存储微信cid数据(百万级别)B+树叶子节点将仅仅有一个B+树非叶节点的指针指向这里。于是。当你查微信cid相关的数据时,全部微信cid将被返回给mysqlserver进行where过滤了,效率上讲,这是非常恐怖的。

索引确实还是被用上了。不然会造成全表扫描。可是这个数据设计的有问题。B+树的查找效率是O(LogN)的,可是遇上这个数据,立马变成O(N),相当于一个局部全表扫描。

那么合理的猜測。仅仅要有新增的微信cid,微信cid的查询仅仅会变的更慢。

引申,更佳的代码 practice:

varchar,blob, text等边长数据建索引的时候。数据库会自己主动建前缀索引,于是B+树不会索引整个字段的部分。非常多同学喜欢用前缀作为字符串的标志,这次要注意了,有前车之鉴了。前缀存入mysql之后会减少检索效率,前缀越长。B+树查询的效率越低。

这里给出代码的建议:

1.将前缀作为后缀,startWith改为endWith

2.不要尝试后缀模糊搜索,like "%.com",这样的做法更糟糕,全然用不了索引,于是全表扫描。

时间: 2024-12-09 15:43:13

字符串的公共前缀对Mysql B+树查询影响回溯分析的相关文章

MySQL知识树-查询语句

在日常的web应用开发过程中,一定会涉及到数据库方面的操作,其中查询又是占绝大部分的.我们不仅要会写查询,最好能系统的学习下与查询相关的知识点,这篇文章我们就来一起看看MySQL查询知识相关的树是什么样的. MySQL查询知识树: 一.查询的种类 二.查询的原理 三.查询的应用场景 四.查询的效率比较 五.如何进行查询优化 六.与查询相关的知识扩展 一.查询的种类 MySQL的查询可以分为内连接查询.左连接查询.右连接查询.联合查询. ①内连接是通过关联表中共有的列来匹配出记录,查询出来的数据是

MySQL统计信息查询慢问题分析

起因 在MySQL服务器运行mysqld_exporter后,发现数据库的中活跃连接数暴增,而且都是来自于mysqld_exporter的慢查询,语句如下: SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE, ifnull(ENGINE, 'NONE') as ENGINE, ifnull(VERSION, '0') as VERSION, ifnull(ROW_FORMAT, 'NONE') as ROW_FORMAT, ifnull(TABLE_ROWS

14. Longest Common Prefix (最长公共前缀)

一.Description: Write a function to find the longest common prefix string amongst an array of strings. public class Solution { public String longestCommonPrefix(String[] strs) { } } 二.Solutions: 1.思路: 开始想的是数组中每两个相邻字符串进行比较,并取比较的最短的前缀作为整个数组的最短前缀.时间复杂度为

leetcode_最长公共前缀

题目:Write a function to find the longest common prefix string amongst an array of strings. 题解:给出的函数为:char* longestCommonPrefix(char** strs, int strsSize) 其中参数char** strs表示字符串数字,int strsSize表示有多少个字符串 题目的要求就是在这strsSize个字符串中找出最长的公共前缀,例如strsSize=3,字符串如下图时

带前缀修改的字典树

在一个 Minecraft 村庄中,村长有这一本小写字母构成的名册(字符串的表), 每个名字旁边都记录着这位村民的声望值,而且有的村民还和别人同名. 随着时间的推移,因为没有村民死亡,这个名册变得十分大. 现在需要您来帮忙维护这个名册,支持下列 4 种操作: 1. 插入新人名 si,声望为 ai 2. 给定名字前缀 pi 的所有人的声望值变化 di 3. 查询名字为 sj 村民们的声望值的和(因为会有重名的) 4. 查询名字前缀为 pj 的声望值的和 输入描述: 第一行为两个整数 0 ≤ N ≤

LeetCode 7最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 示例 1: 输入: ["flower","flow","flight"] 输出: "fl" 示例 2: 输入: ["dog","racecar","car"] 输出: "" 解释: 输入不存在公共前缀. 说明: 所有输入只包含小写字母 a-

最长公共前缀 leetcode 14

方法一(纵向扫描) 解题思路 先计算出数组中最小的字符串长度,这样就避免了越界的情况,思路更加明确,但同时时间复杂度就相应的上升了. 先计算所有字符串在同一列上的字符是否相同,然后依次向后延伸. 代码及注释 class Solution { public: string longestCommonPrefix(vector<string>& strs) { //如果数组中没有字符串,则直接返回空字符串 if(strs.size()==0) return ""; //

hdu1251 字典树的应用(查询公共前缀)

http://acm.hdu.edu.cn/showproblem.php?pid=1251 Problem Description Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀). Input 输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每

[转][LeetCode]Longest Common Prefix ——求字符串的最长公共前缀

题记: 这道题不难但是很有意思,有两种解题思路,可以说一种是横向扫描,一种是纵向扫描. 横向扫描:遍历所有字符串,每次跟当前得出的最长公共前缀串进行对比,不断修正,最后得出最长公共前缀串. 纵向扫描:对所有串,从字符串第0位开始比较,全部相等则继续比较第1,2...n位,直到发生不全部相等的情况,则得出最长公共前缀串. 横向扫描算法实现: //LeetCode_Longest Common Prefix //Written by zhou //2013.11.22 class Solution