橘子学Mysql之成本计算(上)

你是不是经常看到索引失效或者不走索引的话语。你是不是觉得mysql这个东西怎么动不动就不走我的索引了。
甚至我看到一句话:mysql的优化器一思考,上帝都发愁。
种种迹象都说明,mysql的索引是真TMD不好控制。有时候都让人怀疑,mysql底层到底有没有索引控制这一说。
是不是完全放飞了自我,凭心情给你走索引啊。
但是用屁股想想也不可能,mysql是个开源的数据库,它敢这么做,不是找死吗。所以事情的真相,我们在看了《mysql是怎样运行的》这本书之后,得到了一个答案。
mysql先生给出的回复是:索引,谁控制不住,我把她控制的死死的。

一、知识前置

MYSQL中的成本是由两部分组成的,一个是IO成本,一个是CPU成本。
IO成本:innodb中的数据都是放在磁盘里面的,需要我们读取或者处理的时候才会从磁盘加载到内存中。而且这个加载是以mysql中的一个页为单位的。大小innodb默认是16KB。这个从磁盘加载的内存中的过程中的时间消耗就是IO成本。
CPU成本:数据加载到内存后需要读取以及检测数据记录是不是满足你sql中的对应条件,以及要是涉及到排序的时候需要进行排序这些操作的消耗就是cpu成本。
innodb中对于成本是如下规定,读取一个数据页的成本打分是1.0分,一个cpu成本是0.2分。
并且,在读取记录的时候,即使不需要检测记录是不是符合条件这个步骤,成本打分也是0.2。
OK,至此算是一个前置知识。

二、数据准备

创建一个数据表:

CREATE TABLE `single_table` (
	  `id` int(11) NOT NULL AUTO_INCREMENT,
	  `key1` varchar(100) DEFAULT NULL,
	  `key2` int(11) DEFAULT NULL,
	  `key3` varchar(100) DEFAULT NULL,
	  `key_part1` varchar(100) DEFAULT NULL,
	  `key_part2` varchar(100) DEFAULT NULL,
	  `key_part3` varchar(100) DEFAULT NULL,
	  `common_field` varchar(100) DEFAULT NULL,
	  PRIMARY KEY (`id`),
	  UNIQUE KEY `uk_key2` (`key2`) USING BTREE,
	  KEY `idx_key1` (`key1`) USING BTREE,
	  KEY `idx_key3` (`key3`) USING BTREE,
	  KEY `idx_key_part` (`key_part1`,`key_part2`,`key_part3`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8;

这个表,有主键id,唯一索引key2,以及三个普通的二级索引。表里面我用mybais插了一万数据,id是自增的,其余都是随机数。

select * from  single_table where key2 > 900 and key2 < 9000;

我们先以这个简单的sql来分析一下。到底他是怎么计算的,啥时候走索引,啥时候不走呢。

三、成本计算

上面那个sql涉及到一个检索条件,就是在key2这个唯一索引上。

1、全表的代价

innodb中,全表扫描就是把聚簇索引中的记录都一次和给定的搜索条件做对比。然后把结果返回。所以全表扫描需要把聚簇索引所有的数据页都加载的内存中。而计算成本=IO成本+CPU成本。所以计算全表的代价需要先计算两个因子。
1、聚簇索引的页面数。
2、表中的记录数(读取比较需要做cpu成本的计算)。
这个两个信息可以从一个语句中获取。
在这里插入图片描述
这个查询结果里面有两个重要信息,
Rows:就是Innodb引擎给你计算统计的表里面的数据总数,不准确,我插了一万行的。myisam是准确的,Innodb是估计值。这里就估计了9942行。
Data_length:表示的是占用空间的字节数,对于innodb就是聚簇索引占了多大空间在磁盘上。
Data_length=聚簇索引页面数 * 页面大小
已知页面大小是16KB,所以我们可以计算出总共的页面数是:1589248 / 1024 / 16 = 97
所以总共的聚簇索引占的页面总共是97个数据页。
所以到了这一步,我们结合前置知识就能算出全表扫描的时候,需要的成本了。
IO成本:97 * 1.0 + 1.1 = 98.1就是97个页,每个页1分,后面那个1.1不影响大局,不知道是个啥。
CPU成本:9942 * 0.2 + 1.0 = 1989.4 就是9942行,每一行在内存里面做读取然后比对条件,每一个是0.2分,后面那个1.0无所谓了。
所以到这里我们计算出总成本就是 98.1 + 1989.4 = 2087.5
可能你会问了,数据都在叶子节点上,你算页面把树上的内节点的页面也算了,这不是不精确了吗,是的本来ROWS就不精确,这里也是挺放飞自我的。

2、走索引的代价

我们上面说了,存在一个唯一索引key2,所以mysql还会计算一下这个索引的成本,然后去和全表做对比,这个对比结果成本低的就是最后mysql的选择了。
key2的条件是key2 > 900 and key2 < 9000,形成的扫描区间是(900,9000)。
二级索引的查询方式是,索引树上查到,再去聚簇索引树回表查询完整的(我们查的就是*)。
所以mysql需要两部分操作,一个是key2树上的扫描行数,以及需要回表的行数。

1、扫描区间的数量
这里和全表那里不一样,这里有扫描区间,mysql底层认为,一个区间就是一个数据页的成本,我们这里就一个扫描区间(900,9000),所以这里的打分就是1.0
2、回表的记录数
这里的计算规则涉及数据页的个数之类的。都是底层在算,我们是数据库使用者一下就知道了,我这个表里面是用的自增数,所以900-9000这个区间一共数据就是8100条数据。这8100条数据的cpu成本就是8100 * 0.2 + 0.01 = 1620.01
3、回表操作
我们有了回表的记录数,接下来就是要回表了。
Mysql约定,回表的时候,回表一条数据相当于访问一次页面,也就是io打分为1.
我们这里8100条数据,8100 * 1.0 = 8100分
回表之后在局促索引上读取数据,然后做条件对比,条件就是900-9000,所以这个对比就是对应的cpu成本0.2分,总共是8100 * 0.2 = 1620分
所以最后的成本加起来就是:
IO成本:1.0 + 8100 = 8101分
CPU成本:1620.01 + 1620 = 3240.1
所以这个索引的打分就是11341.1

所以我们看到了全表是2087.5 key2索引是11341.1,所以恕我直言,这个查询必走全表查询。
在这里插入图片描述
可见是对的。
那都已经到了这一步了,我们来做个测试吧。

3、手动调整一下

我们知道了计算规则,其实列个不等式就能知道走key2索引的边界条件在哪里?
做个二年级数学题吧。
在这里插入图片描述
我们算出来当扫描行数超过1490行的时候,二级索引的成本低于全表,我们做个测试。
在这里插入图片描述
可见1490确实会走二级索引,但是实际上我测试到1690的时候才是个分界线,但是200行数据对于mysql真是洒洒水,本来就是估算。

四、规避误差

我们上面看到了她的计算方式,其实是有误差的,也就是这个误差,可能有时候你写的sql你想让他走索引,结果他没走,最后你就麻了。
所以生产上,你要是想规避这种问题,可以使用force index强行指定索引。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一份简单的计算机视觉代码,用于实现苹果橘子香蕉分类: ```python import cv2 import numpy as np import os # 准备训练数据 data_dir = './fruits-360' classes = ['Apple', 'Banana', 'Orange'] num_classes = len(classes) data = [] labels = [] for idx, fruit_name in enumerate(classes): path = os.path.join(data_dir, fruit_name) for img in os.listdir(path): img_path = os.path.join(path, img) image = cv2.imread(img_path) image = cv2.resize(image, (32, 32)) data.append(image) labels.append(idx) # 将数据转换为numpy数组 data = np.array(data) labels = np.array(labels) # 对数据进行归一化处理 data = data.astype('float32') / 255.0 # 将数据集分为训练集和测试集 from sklearn.model_selection import train_test_split (trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.25, random_state=42) # 将标签转换为one-hot编码 from keras.utils import to_categorical trainY = to_categorical(trainY, num_classes) testY = to_categorical(testY, num_classes) # 创建模型 from keras.models import Sequential from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout model = Sequential() model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3))) model.add(MaxPooling2D((2, 2))) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(MaxPooling2D((2, 2))) model.add(Conv2D(128, (3, 3), activation='relu')) model.add(MaxPooling2D((2, 2))) model.add(Flatten()) model.add(Dense(128, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(num_classes, activation='softmax')) # 编译模型 model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) # 训练模型 model.fit(trainX, trainY, epochs=10, batch_size=32, validation_data=(testX, testY)) # 在测试集上评估模型 score = model.evaluate(testX, testY, verbose=0) print('Test loss:', score[0]) print('Test accuracy:', score[1]) ``` 这段代码包括了几个关键步骤: 1. 准备训练数据:将图片读入内存中,并将它们的标签存储在一个数组中。 2. 将数据转换为numpy数组,并对它们进行归一化处理。 3. 将数据集分为训练集和测试集。 4. 将标签转换为one-hot编码。 5. 创建卷积神经网络模型,并编译它。 6. 在训练集上训练模型。 7. 在测试集上评估模型。 这段代码的输出将包括测试集上的损失和准确率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值