首页 文章 精选 留言 我的

精选列表

搜索[知识管理],共10000篇文章
优秀的个人博客,低调大师

AUC/ROC:面试中80%都会问的知识点

摘要:ROC/AUC作为机器学习的评估指标非常重要,也是面试中经常出现的问题(80%都会问到) 本文分享自华为云社区《技术干货 | 解决面试中80%问题,基于MindSpore实现AUC/ROC》,原文作者:李嘉琪。 ROC/AUC作为机器学习的评估指标非常重要,也是面试中经常出现的问题(80%都会问到)。其实,理解它并不是非常难,但是好多朋友都遇到了一个相同的问题,那就是:每次看书的时候都很明白,但回过头就忘了,经常容易将概念弄混。还有的朋友面试之前背下来了,但是一紧张大脑一片空白全忘了,导致回答的很差。 我在之前的面试过程中也遇到过类似的问题,我的面试经验是:一般笔试题遇到选择题基本都会考这个率,那个率,或者给一个场景让你选用哪个。面试过程中也被问过很多次,比如什么是AUC/ROC?横轴纵轴都代表什么?有什么优点?为什么要使用它? 我记得在我第一次回答的时候,我将准确率,精准率,召回率等概念混淆了,最后一团乱。回去以后我从头到尾梳理了一遍所有相关概念,后面的面试基本都回答地很好。现在想将自己的一些理解分享给大家,希望读完本篇可以彻底记住ROC/AUC的概念。 ROC的全名叫做Receiver Operating Characteristic,其主要分析工具是一个画在二维平面上的曲线——ROC 曲线。平面的横坐标是false positive rate(FPR),纵坐标是true positive rate(TPR)。对某个分类器而言,我们可以根据其在测试样本上的表现得到一个TPR和FPR点对。这样,此分类器就可以映射成ROC平面上的一个点。调整这个分类器分类时候使用的阈值,我们就可以得到一个经过(0, 0),(1, 1)的曲线,这就是此分类器的ROC曲线。一般情况下,这个曲线都应该处于(0, 0)和(1, 1)连线的上方。因为(0, 0)和(1, 1)连线形成的ROC曲线实际上代表的是一个随机分类器。如果很不幸,你得到一个位于此直线下方的分类器的话,一个直观的补救办法就是把所有的预测结果反向,即:分类器输出结果为正类,则最终分类的结果为负类,反之,则为正类。虽然,用ROC 曲线来表示分类器的性能很直观好用。 可是,人们总是希望能有一个数值来标志分类器的好坏。于是Area Under roc Curve(AUC)就出现了。顾名思义,AUC的值就是处于ROC 曲线下方的那部分面积的大小。通常,AUC的值介于0.5到1.0之间,较大的AUC代表了较好的性能。AUC(Area Under roc Curve)是一种用来度量分类模型好坏的一个标准。 ROC示例曲线(二分类问题): 解读ROC图的一些概念定义: 真正(True Positive , TP)被模型预测为正的正样本; 假负(False Negative , FN)被模型预测为负的正样本; 假正(False Positive , FP)被模型预测为正的负样本; 真负(True Negative , TN)被模型预测为负的负样本。 灵敏度,特异度,真正率,假正率 在正式介绍ROC/AUC之前,我们需要介绍两个指标,这两个指标的选择也正是ROC和AUC可以无视样本不平衡的原因。这两个指标分别是:灵敏度和(1-特异度),也叫做真正率(TPR)和假正率(FPR)。 灵敏度(Sensitivity) = TP/(TP+FN) 特异度(Specificity) = TN/(FP+TN) 其实我们可以发现灵敏度和召回率是一模一样的,只是名字换了而已。 由于我们比较关心正样本,所以需要查看有多少负样本被错误地预测为正样本,所以使用(1-特异度),而不是特异度。 真正率(TPR) = 灵敏度 = TP/(TP+FN) 假正率(FPR) = 1- 特异度 = FP/(FP+TN) 下面是真正率和假正率的示意,我们发现TPR和FPR分别是基于实际表现1和0出发的,也就是说它们分别在实际的正样本和负样本中来观察相关概率问题。 正因为如此,所以无论样本是否平衡,都不会被影响。比如总样本中,90%是正样本,10%是负样本。我们知道用准确率是有水分的,但是用TPR和FPR不一样。这里,TPR只关注90%正样本中有多少是被真正覆盖的,而与那10%毫无关系,同理,FPR只关注10%负样本中有多少是被错误覆盖的,也与那90%毫无关系,所以可以看出: 如果我们从实际表现的各个结果角度出发,就可以避免样本不平衡的问题了,这也是为什么选用TPR和FPR作为ROC/AUC的指标的原因。 或者我们也可以从另一个角度考虑:条件概率。我们假设X为预测值,Y为真实值。那么就可以将这些指标按条件概率表示: 精准率 = P(Y=1 | X=1) 召回率 = 灵敏度 = P(X=1 | Y=1) 特异度 = P(X=0 | Y=0) 从上面三个公式看到:如果我们先以实际结果为条件(召回率,特异度),那么就只需考虑一种样本,而先以预测值为条件(精准率),那么我们需要同时考虑正样本和负样本。所以先以实际结果为条件的指标都不受样本不平衡的影响,相反以预测结果为条件的就会受到影响。 ROC(接受者操作特征曲线) ROC(Receiver Operating Characteristic)曲线,又称接受者操作特征曲线。该曲线最早应用于雷达信号检测领域,用于区分信号与噪声。后来人们将其用于评价模型的预测能力,ROC曲线是基于混淆矩阵得出的。 ROC曲线中的主要两个指标就是真正率和假正率,上面也解释了这么选择的好处所在。其中横坐标为假正率(FPR),纵坐标为真正率(TPR),下面就是一个标准的ROC曲线图。 ROC曲线的阈值问题 与前面的P-R曲线类似,ROC曲线也是通过遍历所有阈值来绘制整条曲线的。如果我们不断的遍历所有阈值,预测的正样本和负样本是在不断变化的,相应的在ROC曲线图中也会沿着曲线滑动。 如何判断ROC曲线的好坏? 改变阈值只是不断地改变预测的正负样本数,即TPR和FPR,但是曲线本身是不会变的。那么如何判断一个模型的ROC曲线是好的呢?这个还是要回归到我们的目的:FPR表示模型虚报的响应程度,而TPR表示模型预测响应的覆盖程度。我们所希望的当然是:虚报的越少越好,覆盖的越多越好。所以总结一下就是TPR越高,同时FPR越低(即ROC曲线越陡),那么模型的性能就越好。参考如下动态图进行理解。 ROC曲线无视样本不平衡 前面已经对ROC曲线为什么可以无视样本不平衡做了解释,下面我们用动态图的形式再次展示一下它是如何工作的。我们发现:无论红蓝色样本比例如何改变,ROC曲线都没有影响。 AUC(曲线下的面积) 为了计算 ROC 曲线上的点,我们可以使用不同的分类阈值多次评估逻辑回归模型,但这样做效率非常低。幸运的是,有一种基于排序的高效算法可以为我们提供此类信息,这种算法称为曲线下面积(Area Under Curve)。 比较有意思的是,如果我们连接对角线,它的面积正好是0.5。对角线的实际含义是:随机判断响应与不响应,正负样本覆盖率应该都是50%,表示随机效果。ROC曲线越陡越好,所以理想值就是1,一个正方形,而最差的随机判断都有0.5,所以一般AUC的值是介于0.5到1之间的。 AUC的一般判断标准 0.5 - 0.7:效果较低,但用于预测股票已经很不错了0.7 - 0.85:效果一般0.85 - 0.95:效果很好0.95 - 1:效果非常好,但一般不太可能 AUC的物理意义 曲线下面积对所有可能的分类阈值的效果进行综合衡量。曲线下面积的一种解读方式是看作模型将某个随机正类别样本排列在某个随机负类别样本之上的概率。以下面的样本为例,逻辑回归预测从左到右以升序排列: 好了,原理已经讲完,上MindSpore框架的代码。 MindSpore代码实现(ROC) """ROC""" import numpy as np from mindspore._checkparam import Validator as validator from .metric import Metric class ROC(Metric): def __init__(self, class_num=None, pos_label=None): super().__init__() # 分类数为一个整数 self.class_num = class_num if class_num is None else validator.check_value_type("class_num", class_num, [int]) # 确定正类的整数,对于二分类问题,它被转换为1。对于多分类问题,不应设置此参数,因为它在[0,num_classes-1]范围内迭代更改。 self.pos_label = pos_label if pos_label is None else validator.check_value_type("pos_label", pos_label, [int]) self.clear() def clear(self): """清除历史数据""" self.y_pred = 0 self.y = 0 self.sample_weights = None self._is_update = False def _precision_recall_curve_update(self, y_pred, y, class_num, pos_label): """更新曲线""" if not (len(y_pred.shape) == len(y.shape) or len(y_pred.shape) == len(y.shape) + 1): raise ValueError("y_pred and y must have the same number of dimensions, or one additional dimension for" " y_pred.") # 二分类验证 if len(y_pred.shape) == len(y.shape): if class_num is not None and class_num != 1: raise ValueError('y_pred and y should have the same shape, but number of classes is different from 1.') class_num = 1 if pos_label is None: pos_label = 1 y_pred = y_pred.flatten() y = y.flatten() # 多分类验证 elif len(y_pred.shape) == len(y.shape) + 1: if pos_label is not None: raise ValueError('Argument `pos_label` should be `None` when running multiclass precision recall ' 'curve, but got {}.'.format(pos_label)) if class_num != y_pred.shape[1]: raise ValueError('Argument `class_num` was set to {}, but detected {} number of classes from ' 'predictions.'.format(class_num, y_pred.shape[1])) y_pred = y_pred.transpose(0, 1).reshape(class_num, -1).transpose(0, 1) y = y.flatten() return y_pred, y, class_num, pos_label def update(self, *inputs): """ 更新预测值和真实值。 """ # 输入数量的校验 if len(inputs) != 2: raise ValueError('ROC need 2 inputs (y_pred, y), but got {}'.format(len(inputs))) # 将输入转为numpy y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) # 更新曲线 y_pred, y, class_num, pos_label = self._precision_recall_curve_update(y_pred, y, self.class_num, self.pos_label) self.y_pred = y_pred self.y = y self.class_num = class_num self.pos_label = pos_label self._is_update = True def _roc_(self, y_pred, y, class_num, pos_label, sample_weights=None): if class_num == 1: fps, tps, thresholds = self._binary_clf_curve(y_pred, y, sample_weights=sample_weights, pos_label=pos_label) tps = np.squeeze(np.hstack([np.zeros(1, dtype=tps.dtype), tps])) fps = np.squeeze(np.hstack([np.zeros(1, dtype=fps.dtype), fps])) thresholds = np.hstack([thresholds[0][None] + 1, thresholds]) if fps[-1] <= 0: raise ValueError("No negative samples in y, false positive value should be meaningless.") fpr = fps / fps[-1] if tps[-1] <= 0: raise ValueError("No positive samples in y, true positive value should be meaningless.") tpr = tps / tps[-1] return fpr, tpr, thresholds # 定义三个列表 fpr, tpr, thresholds = [], [], [] for c in range(class_num): preds_c = y_pred[:, c] res = self.roc(preds_c, y, class_num=1, pos_label=c, sample_weights=sample_weights) fpr.append(res[0]) tpr.append(res[1]) thresholds.append(res[2]) return fpr, tpr, thresholds def roc(self, y_pred, y, class_num=None, pos_label=None, sample_weights=None): """roc""" y_pred, y, class_num, pos_label = self._precision_recall_curve_update(y_pred, y, class_num, pos_label) return self._roc_(y_pred, y, class_num, pos_label, sample_weights) def (self): """ 计算ROC曲线。返回的是一个元组,由`fpr`、 `tpr`和 `thresholds`组成的元组。 """ if self._is_update is False: raise RuntimeError('Call the update method before calling .') y_pred = np.squeeze(np.vstack(self.y_pred)) y = np.squeeze(np.vstack(self.y)) return self._roc_(y_pred, y, self.class_num, self.pos_label) 使用方法如下: 二分类的例子 import numpy as np from mindspore import Tensor from mindspore.nn.metrics import ROC # binary classification example x = Tensor(np.array([3, 1, 4, 2])) y = Tensor(np.array([0, 1, 2, 3])) metric = ROC(pos_label=2) metric.clear() metric.update(x, y) fpr, tpr, thresholds = metric.() print(fpr, tpr, thresholds) [0., 0., 0.33333333, 0.6666667, 1.] [0., 1, 1., 1., 1.] [5, 4, 3, 2, 1] 多分类的例子 import numpy as np from mindspore import Tensor from mindspore.nn.metrics import ROC # multiclass classification example x = Tensor(np.array([[0.28, 0.55, 0.15, 0.05], [0.10, 0.20, 0.05, 0.05], [0.20, 0.05, 0.15, 0.05],0.05, 0.05, 0.05, 0.75]])) y = Tensor(np.array([0, 1, 2, 3])) metric = ROC(class_num=4) metric.clear() metric.update(x, y) fpr, tpr, thresholds = metric.() print(fpr, tpr, thresholds) [array([0., 0., 0.33333333, 0.66666667, 1.]), array([0., 0.33333333, 0.33333333, 1.]), array([0., 0.33333333, 1.]), array([0., 0., 1.])] [array([0., 1., 1., 1., 1.]), array([0., 0., 1., 1.]), array([0., 1., 1.]), array([0., 1., 1.])] [array([1.28, 0.28, 0.2, 0.1, 0.05]), array([1.55, 0.55, 0.2, 0.05]), array([1.15, 0.15, 0.05]), array([1.75, 0.75, 0.05])] MindSpore代码实现(AUC) """auc""" import numpy as np def auc(x, y, reorder=False): """ 使用梯形法则计算曲线下面积(AUC)。这是一个一般函数,给定曲线上的点。计算ROC曲线下的面积。 """ # 输入x是由ROC曲线得到的fpr值或者一个假阳性numpy数组。如果是多类的,这是一个这样的list numpy,每组代表一类。 # 输入y是由ROC曲线得到的tpr值或者一个真阳性numpy数组。如果是多类的,这是一个这样的list numpy,每组代表一类。 if not isinstance(x, np.ndarray) or not isinstance(y, np.ndarray): raise TypeError('The inputs must be np.ndarray, but got {}, {}'.format(type(x), type(y))) # 检查所有数组的第一个维度是否一致。检查数组中的所有对象是否具有相同的形状或长度。 _check_consistent_length(x, y) # 展开列或1d numpy数组。 x = _column_or_1d(x) y = _column_or_1d(y) # 进行校验 if x.shape[0] < 2: raise ValueError('At least 2 points are needed to compute the AUC, but x.shape = {}.'.format(x.shape)) direction = 1 if reorder: order = np.lexsort((y, x)) x, y = x[order], y[order] else: dx = np.diff(x) if np.any(dx < 0): if np.all(dx 1: raise ValueError("Found input variables with inconsistent numbers of samples: {}." .format([int(length) for length in lengths])) 使用方法如下: 利用ROC的fpr, tpr值求auc import numpy as np from mindspore.nn.metrics import auc x = Tensor(np.array([[3, 0, 1], [1, 3, 0], [1, 0, 2]])) y = Tensor(np.array([[0, 2, 1], [1, 2, 1], [0, 0, 1]])) metric = ROC(pos_label=1) metric.clear() metric.update(x, y) fpr, tpr, thre = metric.eval() # 利用ROC的fpr, tpr值求auc output = auc(fpr, tpr) print(output) 0.45 点击关注,第一时间了解华为云新鲜技术~

优秀的个人博客,低调大师

MySQL性能调优总结的19个知识点!

前言 Mysql性能优化就算通过合理安排资源,调整系统参数使MYSQL运行更快,更节省资源。MYSQL性能优化包括查询速度优化,更新速度优化,mysql服务器优化等等。此处,介绍以下几个优化。包含,性能优化的介绍,查询优化,数据库结构优化,mysql服务器优化。 Mysql优化,一方面是找出系统的瓶颈,提高mysql数据库整体的性能,另外一个方面需要合理的结构设计和参数调整,以提高用户操作响应的速度。同时还要尽可能节省系统资源,以便系统可以提供更大负荷的服务。mysql数据库优化是多方面的,原则是减少系统的瓶颈,减少资源的占用,增加系统反应的速度。小编这里总结了一份详细MySQL的思维导图以及MySQL笔记500多页资料集锦 关注公众号:麒麟改bug。 1、为查询优化你的查询 大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。 这里最主要的问题是,对于程序员来说,这个事情是很容易被忽略的。因为,我们某些查询语句会让MySQL不使用缓存。请看下面的示例: // 查询缓存不开启$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");// 开启查询缓存$today = date("Y-m-d");$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'"); 复制代码 上面两条SQL语句的差别就是 CURDATE() ,MySQL的查询缓存对这个函数不起作用。所以,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所需要的就是用一个变量来代替MySQL的函数,从而开启缓存。 2、EXPLAIN 你的SELECT查询 使用EXPLAIN关键字可以让你知道MySQL是如何处理你的SQL语句的。 有表关联的查询,如下列: select username, group_namefrom users ujoins groups g on (u.group_id = g.id) 复制代码 发现查询缓慢,然后在group_id字段上增加索引,则会加快查询 3、当只要一行数据时使用LIMIT 1 当你查询表的有些时候,你已经知道结果只会有一条结果,单因为你可能需要去fetch游标,或是你也许会去检查返回的记录数。 在这种情况下,加上LIMIT 1 可以增加性能。这样一样, MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查找下一条符合记录的数据。 下面的示例,只是为了找一下是否有“中国”的用户,很明显,后面的会比前面的更有效率。(请注意,第一条中是Select *,第二条是Select 1) // 没有效率的:$r = mysql_query("SELECT * FROM user WHERE country = 'China'");if (mysql_num_rows($r) > 0) { // ...}// 有效率的:$r = mysql_query("SELECT 1 FROM user WHERE country = 'China' LIMIT 1");if (mysql_num_rows($r) > 0) {// ...} 复制代码 4、为搜索字段建索引 索引并不一定就是给主键或是唯一的字段。如果在你的表中,有某个字段你总要会经常用来做搜索,那么,请为其建立索引吧。 5、在Join表的时候使用相当类型的列,并将其索引 如果你的应用程序有很多JOIN查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。 而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把DECIMAL字段和一个INT字段JOIN在一起,MYSQL就无法使用他们的索引。对于那些STRING类型,还需要有相同的字符集才行(两个表的字符集有可能不一样) 6、千万不要ORDER BY RAND() 7、避免SELECT * 从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。 所以,你应该养成一个需要什么就取什么的好的习惯。 // 不推荐$r = mysql_query("SELECT * FROM user WHERE user_id = 1");$d = mysql_fetch_assoc($r);echo "Welcome {$d['username']}"; // 推荐$r = mysql_query("SELECT username FROM user WHERE user_id = 1");$d = mysql_fetch_assoc($r);echo "Welcome {$d['username']}"; 复制代码 8、永远为两张表设置一个ID 我们应该为数据库里的每张表都设置一个ID作为其主键,而最好的是一个INT型(推荐使用UNSIGNED),并设置上自动增长的AUTO INCREMENT标志。就算是你 users 表有一个主键叫 “email”的字段,你也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。 复制代码 而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区…… 9、使用 ENUM 而不是 VARCHAR ? ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。 如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。 10、从 PROCEDURE ANALYSE() 取得建议 ? PROCEDURE ANALYSE() 会让 MySQL 帮你去分析你的字段和其实际的数据,并会给你一些有用的建议。只有表中有实际的数据,这些建议才会变得有用,因为要做一些大的决定是需要有数据作为基础的。 例如,如果你创建了一个 INT 字段作为你的主键,然而并没有太多的数据,那么,PROCEDURE ANALYSE()会建议你把这个字段的类型改成 MEDIUMINT 。或是你使用了一个 VARCHAR 字段,因为数据不多,你可能会得到一个让你把它改成 ENUM 的建议。这些建议,都是可能因为数据不够多,所以决策做得就不够准。 11、尽可能的使用 NOT NULL 除非你有一个很特别的原因去使用 NULL 值,你应该总是让你的字段保持 NOT NULL。这看起来好像有点争议,请往下看。 首先,问问你自己“Empty”和“NULL”有多大的区别(如果是INT,那就是0和NULL)?如果你觉得它们之间没有什么区别,那么你就不要使用NULL。(你知道吗?在 Oracle 里,NULL 和 Empty 的字符串是一样的!) 不要以为 NULL 不需要空间,其需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。 当然,这里并不是说你就不能使用NULL了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL值。 下面摘自MySQL自己的文档 “NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.” 12、把IP地址存成 UNSIGNED INT 很多程序员都会创建一个 VARCHAR(15) 字段来存放字符串形式的IP而不是整形的IP。如果你用整形来存放,只需要4个字节,并且你可以有定长的字段。而且,这会为你带来查询上的优势,尤其是当你需要使用这样的WHERE条件:IP between ip1 and ip2。 我们必需要使用UNSIGNED INT,因为 IP地址会使用整个32位的无符号整形 13、固定长度的表会更快 如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。 固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。 并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。 14、垂直分割 “垂直分割”是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段的数目,从而达到优化的目的。(以前,在银行做过项目,见过一张表有100多个字段,很恐怖) 示例一:在Users表中有一个字段是家庭地址,这个字段是可选字段,相比起,而且你在数据库操作的时候除了个人信息外,你并不需要经常读取或是改写这个字段。那么,为什么不把他放到另外一张表中呢? 这样会让你的表有更好的性能,大家想想是不是,大量的时候,我对于用户表来说,只有用户ID,用户名,口令,用户角色等会被经常使用。小一点的表总是会有好的性能。 示例二: 你有一个叫 “last_login” 的字段,它会在每次用户登录时被更新。但是,每次更新时会导致该表的查询缓存被清空。所以,你可以把这个字段放到另一个表中,这样就不会影响你对用户ID,用户名,用户角色的不停地读取了,因为查询缓存会帮你增加很多性能。 另外,你需要注意的是,这些被分出去的字段所形成的表,你不会经常性地去Join他们,不然的话,这样的性能会比不分割时还要差,而且,会是极数级的下降。 15、拆分大的 DELETE 或 INSERT 语句 如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。 Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。 如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让你泊WEB服务Crash,还可能会让你的整台服务器马上掛了。 所以,如果你有一个大的处理,你定你一定把其拆分,使用 LIMIT 条件是一个好的方法。下面是一个示例: while (1) {//每次只做1000条mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");if (mysql_affected_rows() == 0) { // 没得可删了,退出! break;}// 每次都要休息一会儿usleep(50000);} 复制代码 16、 越小的列会越快 对于大多数的数据库引擎来说,硬盘操作可能是最重大的瓶颈。所以,把你的数据变得紧凑会对这种情况非常有帮助,因为这减少了对硬盘的访问。 参看 MySQL 的文档 Storage Requirements 查看所有的数据类型。 如果一个表只会有几列罢了(比如说字典表,配置表),那么,我们就没有理由使用 INT 来做主键,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 会更经济一些。如果你不需要记录时间,使用 DATE 要比 DATETIME 好得多。 当然,你也需要留够足够的扩展空间,不然,你日后来干这个事,你会死的很难看,参看Slashdot的例子(2009年11月06日),一个简单的ALTER TABLE语句花了3个多小时,因为里面有一千六百万条数据。 17、选择一个正确的存储引擎 在 MySQL 中有两个存储引擎 MyISAM 和 InnoDB,每个引擎都有利有弊。酷壳以前文章《MySQL: InnoDB 还是 MyISAM?》讨论和这个事情。 MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。 InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。 18、小心“永久链接” “永久链接”的目的是用来减少重新创建MySQL链接的次数。当一个链接被创建了,它会永远处在连接的状态,就算是数据库操作已经结束了。而且,自从我们的Apache开始重用它的子进程后——也就是说,下一次的HTTP请求会重用Apache的子进程,并重用相同的 MySQL 链接。 PHP手册:mysql_pconnect() 在理论上来说,这听起来非常的不错。但是从个人经验(也是大多数人的)上来说,这个功能制造出来的麻烦事更多。因为,你只有有限的链接数,内存问题,文件句柄数,等等。 而且,Apache 运行在极端并行的环境中,会创建很多很多的了进程。这就是为什么这种“永久链接”的机制工作地不好的原因。在你决定要使用“永久链接”之前,你需要好好地考虑一下你的整个系统的架构。 参考 19、当查询较慢的时候,可用Join来改写一下该查询来进行优化 mysql> select sql_no_cache * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151) ; Empty set (18.87 sec) mysql> select sql_no_cache a.* from guang_deal_outs a inner join guang_deals b on a.deal_id = b.id where b.id = 100017151; Empty set (0.01 sec)原因mysql> desc select sql_no_cache * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151) ;+----+--------------------+-----------------+-------+---------------+---------+---------+-------+----------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+--------------------+-----------------+-------+---------------+--------- +---------+-------+----------+-------------+| 1 | PRIMARY | guang_deal_outs | ALL | NULL | NULL | NULL | NULL | 18633779 | Using where || 2 | DEPENDENT SUBQUERY | guang_deals | const | PRIMARY | PRIMARY | 4 | const | 1 | Using index |+----+--------------------+-----------------+-------+---------------+--------- +---------+-------+----------+-------------+2 rows in set (0.04 sec)mysql> desc select sql_no_cache a.* from guang_deal_outs a inner join guang_deals b on a.deal_id = b.id where b.id = 100017151;+----+-------------+-------+-------+---------------------- +----------------------+---------+-------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-------+-------+---------------------- +----------------------+---------+-------+------+-------------+| 1 | SIMPLE | b | const | PRIMARY | PRIMARY | 4 | const | 1 | Using index || 1 | SIMPLE | a | ref | idx_guang_dlout_dlid | idx_guang_dlout_dlid | 4 | const | 1 | |+----+-------------+-------+-------+---------------------- +----------------------+---------+-------+------+-------------+ 2 rows in set (0.05 sec) 复制代码 其实在 guang_deal_outs 在deal_id 上也是有索引的。 其实我想把子查询设置为 select * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151); 复制代码 变成下面的样子 select * from guang_deal_outs where deal_id in (100017151); 复制代码 但不幸的是,实际情况正好相反。MySQL试图让它和外面的表产生联系来“帮助”优化查询,它认为下面的exists形式更有效率 select * from guang_deal_outs where exists (select * from guang_deals where id = 100017151 and id = guang_deal_outs.deal_id); 复制代码 这种in子查询的形式,在外部表(比如上面的guang_deals)数据量比较大的时候效率是很差的(如果对于较小的表,不会造成显著地影响) 文章到这里就结束了! 小编这里总结了【免费领取 MySQL笔记500多页资料集锦+1000道互联网大厂Java工程师面试题、spring、mybatis、jvm,Zookeeper,spring】 关注公众号:麒麟改bug,编程的世界永远向所有热爱编程的人开放,这是一个自由,平等,共享的世界,我始终是这样坚信的。

优秀的个人博客,低调大师

Java 并发底层知识,锁获取超时机制知多少?

当我们在使用Java进行网络编程时经常会遇到很多超时的概念,比如一个浏览器请求过程就可能会产生很多超时的地方,当我们在浏览器发起一个请求后,网络socket读写可能会超时,web服务器响应可能会超时,数据库查询可能会超时。而对于Java并发来说,与超时相关的内容主要是线程等待超时和获取锁超时,比如调用Object.wait(long)就会使线程进入等待状并在指定时间后等待超时。  此篇主要讲解Java内置锁的获取操作的超时机制。当大量线程对某一锁竞争时可能导致某些线程在很长一段时间都获取不了锁,在某些场景下可能希望如果线程在一段时间内不能成功获取锁就取消对该锁的等待以提高性能,这时就需要用到超时机制。 Synchronized不支持超时 我们先看Java从语法层提供的并发锁——synchronized关键词,synchronized对我们来说是相当熟悉的了,它是Java内置的锁方案。在Java的世界,每个对象都关联着一个内置锁,当线程要访问被synchronized修饰的对象时都必须先获得其对应的锁才能继续访问,否则将一直等待直到该锁被其它线程所释放。普通对象和对象的方法都关联有对应的内置锁,所以它们都可以被synchronized修饰。  虽然synchronized使用很方便,但其存在一个缺点,那就是锁获取操作不支持超时机制。在并发的情况下,多个线程会去竞争被synchronized所修饰对应的锁对象,可能存在某个线程一直获取不到锁而一直处于阻塞等待状态。而这个处于阻塞状态的线程唯一能做的就是一直等待,我们没有办法设置一个等待超时时间。以下面的代码为例,线程一会先成功获取锁,在输出“Thread1 gets the lock”后进入睡眠,睡眠的时间很长。线程二较晚启动,它尝试获取锁,但该锁已被线程一所持有,所以线程一将永远获取不到锁而一直等待。  AQS 同步器超时机制 在JDK1.5之前还没有JUC工具,当时的并发控制只能通过上述的synchronized关键词实现锁,但它对超时取消的控制力不从心。JDK1.5开始引入的JUC工具则完美地解决了此问题,主要是因为AQS同步器提供了锁获取超时的支持。我们知道AQS同步器使用了队列的结构来处理等待的线程,AQS获取锁的超时机制大致如下图所示。首先多个线程竞争锁,因为锁已被其它线程持有,所以通过自旋的CAS操作将各自线程添加到队尾。其次是在线程添加到队列后,每个线程节点都各自轮询前一节点看是否轮到自己获取锁。假如这里线程2设置了超时机制,且线程2在超时时间内都获取不到锁,则该线程对应的节点将被取消。最终线程2因为获取锁超时而被取消。 超时实现逻辑 为了更精确地保证时间间隔的准确性,实现时使用了更为精确的System.nanoTime()方法,它能精确到纳秒级别。总体而言,超时机制的思想就是先计算deadline时间,然后在不断进行锁检查操作中计算是否已经到deadline时间,如果已到deadline时间则取消队列中的该节点并跳出循环。 AQS的超时控制有两点必须要注意: 一是超时时间包括了竞争入队的时间,如果竞 争入队就把超时时间消耗完的话则直接当作超时处理; 另一个是关于spinForTimeoutThreshold变量阀值,它是决定使用自旋方式消耗时间还是使用系统阻塞方式消耗时间的分割线。 JUC工具包作者通过测试将默认值设置为1000ns,即如果在成功插入等待队列后剩余时间大于1000ns则调用系统底层阻塞。否则不调用系统底层阻塞,取而代之的是仅仅让其在Java层不断循环消耗时间,这属于性能优化的措施。 总结 Java内置的synchronized关键词虽然提供了并发锁功能,但它却存在不支持超时的缺点。而AQS同步器则在获取锁的过程中提供了超时机制,同时我们深入分析了AQS获取锁超时的具体实现原理。获取锁超时的支持让Java在并发方面提供了更完善的机制,能满足开发者更多的并发策略需求。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册