博客

  • Web安全之P3P

    P3P 全称是 The Platform for Privacy Preferences,控制用户在在浏览网页时,如果网页中包含第三方站点资源,是否接受第三方站点设置的 cookie。

    举个例子:

    user 访问 www.main.com, 该站点加载了第三方网站 www.third.com 的网页(通过iframe)或者静态资源(css, js, img),是否接受 www.third.com 设置的 cookie。

    默认情况下不允许保存第三方 cookie,假如第三方站点在 header 中设置了 P3P 相关信息,则可能允许跨域访问隐私数据

    IE 浏览器隐私设置:

    IE 浏览器第三方 cookie 策略

    Chrome 浏览器隐私设置:

    Chrome 浏览器第三方 cookie 策略

    配合这两幅截图说明第三方 cookie 在 IE、Chrome 中是否有效。两幅截图显示 IE 阻止第三方 cookie,Chrome 接受第三方 cookie。

    代码示例:

    www.main.com setcookie

    // file http://www.main.com/setcookie.php
    <script src="http://www.main.com/setcookie.php"></script>
    <!-- <iframe src="http://www.main.com/setcookie.php"></iframe> -->
    

    www.third.com setcookie

    // header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
    setcookie('p3p_cookie_key', 'time_' . time(), time() + 60, '/', '.third.com');
    

    www.third.com getcookie

    // file http://www.third.com/getcookie.php
    echo 'p3p_cookie_key:' . $_COOKIE['p3p_cookie_key'];
    

    访问 http://www.main.com/setcookie.php,网页会通过 iframe 或者 script 访问 http://www.third.com/setcookie.php, 此时浏览器不一定接受来自 www.third.com 的 cookie。也就是立即访问 http://www.third.com/getcookie.php,可能获取不到 cookie 信息。

    当 www.third.com setcookie.php 输出 P3P 头信息时:

    1. 访问 http://www.main.com/setcookie.php
    2. 访问 http://www.third.com/getcookie.php 可以获取到 cookie p3p_cookie_key

    网页上所有资源浏览器都可以访问,但出于安全考虑,浏览器对此进行了限制,产生一些安全词汇。

    P3P Header is present [引用]

    CP=”CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR”
    Compact Policy token is present. A trailing ‘o’ means opt-out, a trailing ‘i’ means opt-in.
    CURa Information is used to complete the activity for which it was provided.
    ADMa Information may be used for the technical support of the Web site and its computer system.
    DEVa Information may be used to enhance, evaluate, or otherwise review the site, service, product, or market.
    PSAo Information may be used to create or build a record of a particular individual or computer that is tied to a pseudonymous identifier, without tying identified data (such as name, address, phone number, or email address) to the record. This profile will be used to determine the habits, interests, or other characteristics of individuals for purpose of research, analysis and reporting, but it will not be used to attempt to identify specific individuals.
    PSDo Information may be used to create or build a record of a particular individual or computer that is tied to a pseudonymous identifier, without tying identified data (such as name, address, phone number, or email address) to the record. This profile will be used to determine the habits, interests, or other characteristics of individuals to make a decision that directly affects that individual, but it will not be used to attempt to identify specific individuals.
    OUR We share information with ourselves and/or entities acting as our agents or entities for whom we are acting as an agent.
    BUS Info is retained under a service provider’s stated business practices. Sites MUST have a retention policy that establishes a destruction time table. The retention policy MUST be included in or linked from the site’s human-readable privacy policy.
    UNI Non-financial identifiers, excluding government-issued identifiers, issued for purposes of consistently identifying or recognizing the individual. These include identifiers issued by a Web site or service.
    PUR Information actively generated by the purchase of a product or service, including information about the method of payment.
    INT Data actively generated from or reflecting explicit interactions with a service provider through its site — such as queries to a search engine, or logs of account activity.
    DEM Data about an individual’s characteristics — such as gender, age, and income.
    STA Mechanisms for maintaining a stateful session with a user or automatically recognizing users who have visited a particular site or accessed particular content previously — such as HTTP cookies.
    PRE Data about an individual’s likes and dislikes — such as favorite color or musical tastes.
    COM Information about the computer system that the individual is using to access the network — such as the IP number, domain name, browser type or operating system.
    NAV Data passively generated by browsing the Web site — such as which pages are visited, and how long users stay on each page.
    OTC Other types of data not captured by the above definitions.
    NOI Web Site does not collected identified data.
    DSP The privacy policy contains DISPUTES elements.
    COR Errors or wrongful actions arising in connection with the privacy policy will be remedied by the service.
  • Web安全之XSS

    在 Web 安全领域有一个词汇经常被提到——XSS。简单地说,就是网页被不信任源注入了可执行脚本

    比如:

    // a.php 文件内容
    <?php $email = $_GET['email'];?>
    <input type="hidden" name="email" value=<?=$email?>""/>
    
    // 访问 a.php?email=a%40a.com"%20onclick%3D"alert(1)
    
    // 在网页没有采取任何安全策略的情况下会弹出一个对话框
    
    // 最后的火狐浏览器的截图是攻击者希望看到的
    

    以上代码分别在 IE、Chrome、Firefox 中的效果。

    XSS IE

    IE 会检测可能的跨站脚本操作。

    XSS Chrome

    Chrome 也会检测 XSS 攻击,说明了为何出现此提示(its source code was found within the request),也说明了如何更好地工作。

    XSS Firefox

    不是我黑它,真没有,火狐版本 37.0.2。

    这个内联的 onload 并没有包含 <script> 标签,但是它仍然可以执行。这个示例中只是弹出一个对话框,可能还有更严重的破坏行为。

    为了避免出现 XSS 攻击,不能仅仅依靠浏览器帮我们检测,还需要我们有意识地去过滤这些不安全的脚本。

  • HTML5 新增标签

    语义标签

    <article> 定义文章
    <aside> 定义页面内容之外的内容
    <dialog> 定义对话框或窗口
    <figcaption> 定义 figure 标题,类似 <fieldset> 的 <legend>
    <figure> 定义媒体内容以及标题
    <footer> 定义网页或区块的页脚,类似 div.footer
    <header> 定义网页或区块的页眉,类似 div.header
    <hgroup> 定义区块或标题组合
    <nav> 定义导航链接
    <section> 定义区块,区块内部具有关联性
    <time> 定义日期/时间

    多媒体标签

    <audio> 定义声音
    <canvas> 定义图形
    <embed> 定义外部交互内容或插件
    <source> 定义媒体源
    <track> 定义用在媒体播放器中的文本轨道
    <video> 定义视频

    应用标签

    <command> 定义命令按钮,目前不受支持
    <details> 定义元素的细节
    <datalist> 定义下拉列表
    <meter> 定义预定义范围内的度量
    <menu> 定义菜单,在 4.01 中弃用,在 5 中重新定义,目前不受支持
    <menuitem> 定义弹出菜单条目
    <progress> 定义进度
    <summary> 定义 <details> 标题,类似 <fieldset> 的 <legend>
    <wbr> 定义可换行的文本

    其他标签

    <bdi> 定义文本的文本方向,使其脱离其周围文本方向
    <keygen> 定义生成密钥
    <mark> 定义有记号的文本
    <output> 定义输出类型
    <ruby> 定义 ruby 注释
    <rp> 定义若浏览器不支持 ruby 元素显示的内容
    <rt> 定义 ruby 注释的解释
  • JavaScript 排序

    常见排序算法比如:

    • 冒泡排序(属于交换排序)
    • 快速排序(属于交换排序)
    • 直接插入排序(属于插入排序)
    • 希尔排序(属于插入排序)
    • 简单选择排序(属于选择排序)
    • 堆排序(属于选择排序)
    • 归并排序
    • 基数排序

    下面通过 JavaScript 实现各个排序算法。

    冒泡排序

    冒泡排序应该是印象中接触最早的排序算法。冒泡算法核心是顺序比较相邻的两个元素,在升序排列下,如果元素 i 大于 元素 i + 1,则交换两者,第一轮比较到最后一个元素,第二轮比较到倒数第二个元素。直到只剩下第一个元素,比较完毕。

    中间有一个优化是,如果某轮比较没有进行交换,则认为数组已经有序,不需要进行下一轮比较。

    另一个“改进”版本——双向冒泡,为了减少来回的次数。但实际并没有减少比较次数,反而申请了更多的内存空间。

    function sortBubble(array) {
    	var i, j = array.length - 1, tmp, no_change;
    	while (j > 0) {
    		no_change = true;
    		for (i = 0; i < j; i++) {
    			if (array[i] > array[i + 1]) {
    				tmp = array[i];
    				array[i] = array[i + 1];
    				array[i + 1] = tmp;
    				no_change = false;
    			}
    		}
    		if (no_change) {
    			break;
    		}
    	}
    }
    var ar_score = [10, 9, 8, 7, 6];
    sortBubble(ar_score);
    

    快速排序

    快速排序通过设置哨兵,把小于和大于哨兵的数组元素分别组织为数组,再通过递归对两个数组进行快速排序,直到每个数组不能分解(只有一个数组元素)。

    <script type="text/javascript">
    function sortQuick(array) {
    	var i = 0, j = array.length - 1, index = 0, guard = array[index];
    	var ar_left, ar_right;
    	while (j > i) {
    		while (j > index) {
    			if (array[j] < guard) {
    				array[index] = array[j];
    				array[j] = guard;
    				index = j;
    				break;
    			}
    			j--;
    		}
    		while (i < index) {
    			if (array[i] > guard) {
    				array[index] = array[i];
    				array[i] = guard;
    				index = i;
    				break;
    			}
    			i++;
    		}
    	}
    
    	if (i > 0) {
    		ar_left = array.slice(0, i);
    		sortQuick(ar_left);
    		array.splice.apply(array, [0, i].concat(ar_left));
    	}
    
    	if (j < array.length - 1) {
    		j++;
    		ar_right = array.slice(j);
    		sortQuick(ar_right);
    		array.splice.apply(array, [j, array.length - j].concat(ar_right));
    	}
    }
    
    var ar_score = [10, 9, 8, 7, 6];
    sortQuick(ar_score);
    </script>
    

    [10, 9, 8, 7, 6] 交换步骤

    • 第一次排序 [6, 9, 8, 7, 10]
    • 分拆为 [6, 9, 8, 7]、[10], 左侧数组在进行快速排序、右侧数组不需要进行排序
    • 第二次排序 [6, 9, 8, 7] 、[10], 分拆为 [6]、[9, 8, 7]、[10]
    • 第三次排序 [6]、[7, 8, 9]、[10]
    • 第四次排序 [6]、[7, 8], [9], [10]
    • 第五次排序 [6]、[7], [8], [9]、[10]
    • 拼装返回的数组列表得到 [6, 7, 8, 9, 10]

    直接插入排序

    插入排序是在实际生活中使用得比较早的排序,比如试卷评分,把试卷按照分数高低排序。

    插入排序通过设置有序数组,从后续元素中依次比较元素与有序数组中元素的顺序,从而插入到指定位置,最终形成有序数组。

    1. 设置 a[0] 是一个有序数组,无序数组从 a[1…length-1]。length 为数组长度,假定 i = 1
    2. 将 a[i] 插入到有序数组 a[0…i-1]
    3. 将 i++,如果 i 小于 length 则重复第二步
    4. 排序结束
    <script type="text/javascript">
    function sortInsert(array) {
    	var i = 1, j, k, length = array.length, tmp;
    
    	if (!(length > 1)) {
    		return;
    	}
    
    	do {
    		for (j = i - 1; j > -1; j--) {
    			if (array[j] < array[i]) {
    				break;
    			}
    		}
    		if (j != i - 1) {
    			tmp = array[i];
    			for (k = i - 1; k > j; k--) {
    				array[k + 1] = array[k];
    			}
    			array[k + 1] = tmp;
    		}
    		i++;
    	} while (i < length);
    }
    var ar_score = [10, 9, 8, 7, 6];
    sortInsert(ar_score);
    </script>
    

    [10, 9, 8, 7, 6] 插入步骤

    • 第1次排序,设置有序数组 [10],插入数组元素 9,得到 [9, 10]、[8, 7, 6]
    • 第2次排序,有序数组 [9, 10],插入数组元素 8,得到 [8, 9, 10]、[7, 6]
    • 第3次排序,有序数组 [8, 9, 10],插入数组元素 7,得到 [7, 8, 9, 10]、[6]
    • 第4次排序,有序数组 [7, 8, 9, 10],插入数组元素 6,得到 [6, 7, 8, 9, 10]

    直接插入排序在第二步插入时,需要用到查询,上述代码的一个改进点是使用二分查找,降低时间复杂度。

    希尔排序

    希尔排序

    简单选择排序

    简单选择排序

    堆排序

    堆排序

    归并排序

    归并排序

    基数排序

    基数排序

  • IE DOM 高度临界值

    在 IE 浏览器中设置 DOM 元素的高度,并不是一定有效的。比如设置标签的 height 样式,如果这个值足够大,那么在 IE 下就可能是无效的。

    下表展示了 IE 浏览器 DOM 元素的最大高度:

    IE11 DOM 最大高度 1533957px
    IE10 DOM 最大高度 1460873px
    IE9 DOM 最大高度 1472560px
  • JavaScript 数组

    JavaScript 数组成员方法

    • concat
    • entries
    • every
    • filter
    • forEach
    • indexOf
    • join
    • keys
    • lastIndexOf
    • map
    • pop
    • push
    • reduce
    • reduceRight
    • reverse
    • shift
    • slice
    • some
    • sort
    • splice
    • toLocaleString
    • toString
    • unshift

    concat

    var a = [1];
    a.concat(2); // [1, 2]
    a.concat([2]); // [1, 2]
    a.concat([[2]]); // [1, [2]]
    // a is [1]
    

    entries

    var a = ['a', 'b', 'c'];
    var iterator = a.entries(); // iterator is an ArrayIterator
    var row;
    do {
    	row = iterator.next();
    	if (row.done) break;
    
    	console.log(row.value, row.done); // row.value = [index, value]
    	// [0, 'a'], false
    	// [0, 'b'], false
    	// [0, 'c'], false
    } while (true);
    

    every、some、forEach

    var a = [1, 2, 3, 4];
    var callback_context = this;
    a.every(function(element, index, array) {
    	// return false to break the iterate and return false
    	return element > 2;
    }, callback_context); // false
    
    var a = [1, 2, 3, 4];
    var callback_context = this;
    a.some(function(element, index, array) {
    	// return true to break the iterate and return true
    	return element > 2;
    }, callback_context); // true
    
    var a = [1, 2, , 4];
    var callback_context = this;
    a.forEach(function(element, index, array) {
    	// called once for every not undefined element
    }, callback_context); // true
    var b = [];
    a.forEach(function(element) {
    	b.push(element);
    });
    // a is [1, 2, undefined, 4]
    // b is [1, 2, 4]
    

    filter

    var a = [1, 2, 3, 4];
    var callback_context = this;
    a.filter(function(element, index, array) {
    	if (element % 2) {
    		return true;
    	}
    	return false;
    }, callback_context); // [1, 3]
    // a is [1, 2, 3, 4]
    

    keys

    var a = ['a', 'b', 'c'];
    var iterator = a.keys();
    var row;
    do {
    	row = iterator.next();
    	if (row.done) break;
    
    	console.log(row.value, row.done); // row.value = index
    	// 0, false
    	// 1, false
    	// 2, false
    } while (true);
    

    map

    var a = [1, 2, 3, 4];
    var callback_context = this;
    a.map(function(element, index, array) {
    	return element * 2;
    }, callback_context); // [2, 4, 6, 8]
    // a is [1, 2, 3, 4]
    

    push

    var a = ['a'];
    a.push('b'); // 2
    // a is ['a', 'b']
    a.push(['c']); // 3
    // a is ['a', 'b', ['c']]
    

    reduce、reduceRight

    var a = [1, 2, 3, 4];
    a.reduce(function(precious_value, current_value, current_index) {
    	return precious_value + current_value;
    }); // 10
    // precious_value = 1, current_value = 2 precious_value + current_value = 3
    // precious_value = 3, current_value = 3 precious_value + current_value = 6
    // precious_value = 6, current_value = 4 precious_value + current_value = 10
    // 10
    
    var a = [1, 2, 3, 4];
    a.reduce(function(precious_value, current_value, current_index) {
    	return precious_value + current_value;
    }, 5); // 5 is initial value
    // precious_value = 5, current_value = 1 precious_value + current_value = 6
    // precious_value = 6, current_value = 2 precious_value + current_value = 8
    // precious_value = 8, current_value = 3 precious_value + current_value = 11
    // precious_value = 11, current_value = 4 precious_value + current_value = 15
    // 15
    
    // reduceRight performs same as reduce
    var a = [1, 2, 3, 4];
    a.reduce(function(precious_value, current_value, current_index) {
    	return precious_value + current_value;
    }); // 10
    // precious_value = 4, current_value = 3 precious_value + current_value = 7
    // precious_value = 7, current_value = 2 precious_value + current_value = 9
    // precious_value = 9, current_value = 1 precious_value + current_value = 10
    // 10
    

    sort

    var a = [4, 3, 2, 1];
    a.sort(); // [1, 2, 3, 4]
    // a is [1, 2, 3, 4]
    
    var a = ['d', 'c', 'b', 'a'];
    a.sort(function(item1, item2) {
    	if (item1 == item2) {
    		return 0;
    	}
    	if (item1.charCodeAt(0) > item2.charCodeAt(0)) {
    		return 1;
    	}
    	// item1 less than item2
    	return -1;
    	// JavaScript 采取的算法应该是冒泡排序
    });
    

    splice

    var a = ['a', 'b', 'c'];
    a.splice(0, 1); // ['a'] splice(start, delete_count)
    // a is ['b', 'c']
    
    var a = ['a', 'b', 'c'];
    a.splice(0, 1, '1'); // ['a'] splice(start, delete_count, replace_element)
    // a is ['1', 'b', 'c']
    
    var a = ['a', 'b', 'c'];
    a.splice(0, 2, '1', '2'); // ['a', 'b'] splice(start, delete_count, replace_element_1, replace_element_2)
    // a is ['1', '2', 'c']
    
    var a = ['a', 'b', 'c'];
    a.splice(0, 1, '1', '2'); // ['a'] splice(start, delete_count, replace_element_1, replace_element_2)
    // a is ['1', '2', 'b', 'c']
    

    JavaScript Array 还有一些方法,但由于浏览器兼容性,即使如 entries 也较少使用。

  • JavaScript 加号运算符

    JavaScript 加号运算符的作用

    作用 示例 结果
    1, 相加数值 7 + 7 数值 14
    2, 连接字符串 ‘7’ + ‘7’ 字符串 ’77’
    3, 转换操作数为数值 + ‘7’ 数值 7

    1, 两个操作数

    有两个操作数 a + b 时,会相加数值或者连接字符串。

    在 JavaScript 中对两个操作数进行 + 运算时,可能会触发将其转换为原始值(ToPrimitive)、转换为数字(ToNumber)、转换为字符串(ToString)。

    ToPrimitive 转换顺序:

    1. 若操作数是原始值,则返回它;
    2. 若它的 valueOf() 的返回值是原始值,则返回 valueOf() 的返回值;
    3. 若它的 toString() 的返回值是原始值,则返回 toString() 的返回值;
    4. 抛出 TypeError 错误。

    ToPrimitive 的转换顺序并不是完全绝对的,其中第 2 步和第 3 步可以调换,目前只有 Date 对象是在转换为原始值时是先调用 toString,再调用 toValueOf,其余均符合上面的转换顺序。

    JavaScript 的原始值包括:undefined、null、布尔值、数字、字符串。

    a + b

    把操作数 a、b 转换为原始值还不能马上进行 + 运算,还需要再进行 ToNumber 或者 ToString 转换为相同类型。

    ToNumber 转换顺序:

    • undefined => NaN
    • null => 0
    • true => 1
    • false => 0
    • 数字 => 自身
    • 字符串 => 转换为数字,例如 “2.2” 转换为 2.2、”2.2a” 转换为 NaN

    字符串转换为数字可以参考(实际中不是像下面这样做的,但结果一致):

    "2.2" / 1 // 2.2
    "2.a" / 1 // NaN
    

    ToString 转换书序:

    • undefined => “undefined”
    • null => “null”
    • true => “true”
    • false => “false”
    • 数字 => 转换为字符串,例如 1 转换为 “1”、NaN 转换为 “NaN”
    • 字符串 => 自身

    ToString 很好理解,简单地看就是在原来非字符串的两侧加上引号。

    当 a + b 时,先将 a、b 转换为原始值,如果其中有一个是字符串,则将另外一个也转换为字符串,连接两个字符串并返回;将两个操作数转换为数字,相加并返回。

    var a = true;
    var b = false;
    a + b // ? a 原始值是 true, b 原始值是 false, 均不是字符串, 因此是 1
    
    var a = 1;
    var b = [2];
    a + b // ? a 原始值是 1, b 原始值是 "2", 因此是 "12"
    
    var a = 1;
    var b = new Date;
    a + b // ?
    

    2, 一个操作数

    只有一个操作数时,会转换操作数为数值。这个比较好理解,可以按照上面提到的除以 1,结果其实就是操作数除以 1 或者乘以 1。

    3, 看似两个操作数

    var a = {};
    var b = [];
    a + b // 根据转换为原始值的规则, 结果是 "[object Object]"
    
    {} + [] // 结果是 0, 为什么呢
    

    因为 JavaScript 解释器(一般浏览器环境)会将第一行的 {} 代码块忽略掉,因此如下的代码看似两个操作数,实际在浏览器中是一个,即只有 + []

    在 NodeJS 里面就不会将第一行的 {} 代码块忽略掉,其执行结果是 “[object Object]”。

    另外,关于转换为原始值中出现 TypeError,可以进行简单的重现:

    var a = {
        toString: function() {
            return [];
        },
        valueOf: function() {
            return [];
        }
    };
    var b = null;
    a + b // 将会看到 TypeError: Cannot convert object to primitive value
    
  • JavaScript 弱类判断速查表

    JavaScript 弱类 == 判断速查表

    JavaScript 弱类判断

    通过此表可以快速查询弱类判断结果。

    JavaScript == 判断遵循规则如下:

    1. null == undefined => true
    2. 字符串 == 数字 => 先将字符串转换为数字,再比较两者的值
    3. 布尔值 == 非布尔值 => 将 true 转换为 1,false 转换为 0,再进行比较
    4. 对象 == 原始值 => 先将对象转换为原始值,再进行比较。JavaScript 内置类转换为原始值,先尝试调用 valueOf 方法,取得结果不是原始值再调用 toString 方法。日期类 Date 对象特殊,Date 对象只使用 toString 转换为原始值。
    5. 其他比较不相等。

    原始值转换还体现在操作符 +,它可以对两个数值相加,也可以连接两个字符串,更可以转换操作数为数值。当参与 + 运算的操作数只有一个时,得到的结果与 parseFloat 非常相似。区别在于 + 右侧的操作数必须是合法的数值形式,parseFloat 从左到右尽可能地识别数字。

    • + ‘5.2’ => 5.2
    • parseFloat(‘5.2’) => 5.2
    • + ‘5.2.5’ => NaN
    • parseFloat(‘5.2.5’) => 5.2
    • + ‘5.2a’ => NaN
    • parseFloat(‘5.2a’) => 5.2

    + 的趣味更体现在原始值转换中,此处 Date 对象转换原始值只是一个引子。

  • 合理设置联合主键,避免无效索引

    设计有效的 MySQL 联合主键。

    以 MySQL 存储用户笔记为例,设计两种数据库 —— 表结构一致、联合主键顺序不一致。

    联合主键表结构

    t_note_no 表结构

    联合主键表结构

    t_note_yes 表结构

    可以看出两个数据库在数据存储上是一致的,唯一区别是联合主键顺序不一致。

    查询笔记

    查询指定用户的正常状态的笔记:

    select * from t_note_no where user_id=1 and status=1;
    select * from t_note_yes where user_id=1 and status=1;

    以上两条语句在表数据量不大的情况下无法看出区别。

    当数据量达到十万级(实际情况跟机器性能有关)时差异开始体现出来,第一种查询会明显慢。

    联合主键 explain

    使用 explain 语句解释可以发现 “select * from t_note_no where user_id=1 and status=1” 并没有用到主键索引。而 “select * from t_note_yes where user_id=1 and status=1” 是用到了索引。

    explain 语句说明:

    id select 查询的序列号
    select_type select 查询的类型,主要是区别普通查询和联合查询、子查询之类的复杂查询
    table 表示引用的表
    type 表示查询类型
    possible_keys 表示 MySQL 可能使用的索引,没有使用索引时为 NULL
    key 表示 MySQL 实际使用的索引,没有使用索引时为 NULL
    key_len 表示 MySQL 使用的索引的长度,越短越好
    ref 表示使用索引的哪一列
    Extra 额外信息
    • select_type
    • SIMPLE 简单 select,没有使用 union 或子查询
      PRIMARY 最外面的 select
      UNION union 中的第二个或后面的 select 语句
      DEPENDENT UNION union 中的第二个或后面的 select 语句,取决于外面的查询
      UNION RESULT union 的结果
      SUBQUERY 子查询中的第一个 select
      DEPENDENT SUBQUERY 子查询中的第一个 select,取决于外面的查询
      DERIVED 导出表的 select,是 from 子句的子查询
    • type
    • system 系统表,仅有一行,是 const 联接类型的一个特例
      const 表最多有一个匹配行,在该行的列值被认为是常数,只会读取一次,非常快速
      eq_ref 从每个来自前面的表的行组合中读取一行,可能是除了 const 之外最好的联接类型
      ref 从对于每个来自前面的表的行组合读取有匹配索引值的行
      ref_or_null 添加 MySQL 可以专门搜索包含 NULL 值的行,可以理解如同 ref
      index_merge 表示使用了索引合并优化方法
      unique_subquery 表示替换了指定形式【value IN (SELECT primary_key FROM single_table WHERE some_expr)】的 IN 子查询的 ref,是一个索引查找函数,可以完全替换子查询,效率更高
      index_subquery 类似于 unique_subquery。可以替换指定形式【value IN (SELECT key_column FROM single_table WHERE some_expr)】的 IN 子查询,并且是非唯一索引
      range 只检索给定范围的行,使用一个索引来选择行
      index 表示进行完整的索引扫描,因为索引文件通常比数据文件小,所以通常比 ALL 快
      ALL 表示进行完整的表扫描,此为最差的情况

      最好保证查询能达到 ref,再不济也要缩小到一定范围,达到 range。

    • Extra
    • Distinct 匹配到第1行之后停止匹配
      Not exists MySQL 对查询进行 LEFT JOIN 优化,发现1个匹配 LEFT JOIN 标准的行后,不再为前面的的行组合在该表内检查更多的行
      range checked for each record (index map: #) MySQL 没有发现好的可以使用的索引,但发现如果来自前面的表的列值已知,可能部分索引可以使用
      Using filesort MySQL 需要额外的一次传递,以找出如何按排序顺序检索行
      Using index 从只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的列信息
      Using temporary 为了解决查询,MySQL 创建一个临时表来容纳结果
      Using where WHERE 子句用于限制哪一个行匹配下一个表或发送到客户
      Using sort_union(…), Using union(…), Using intersect(…) 这些函数说明如何为 index_merge 联接类型合并索引扫描
      Using index for group-by 类似于访问表的 Using index 方式,Using index for group-by 表示 MySQL 发现了一个索引,可以用来查询 GROUP BY 或 DISTINCT 查询的所有列,而不要额外搜索硬盘访问实际的表

      设计联合主键尽可能根据实际应用考虑。

    不合理的查询导致索引失效

    SQL语句不合理也会导致索引失效,例如:

    select * from t_note_yes where user_id=1 or status=2;

    where 查询条件中虽然 user_id 设置了索引,但是 status 没有设置索引,导致索引失效。而且对 status tinyint 类型的字段设置索引是没有必要为其设置索引。

    知晓了 explain 各个参数的含义,可以快速帮助提高数据库查询性能。

    但是在很多情况下也会导致索引失效。

    如果 where 子句中使用 like,并且在左侧使用了 %,那便没有使用索引。

    SQL WHERE LIKE

    如果 where 子句中使用 or,并且 or 的各个条件只要有一个没有使用到索引,那也会导致整个 SQL 查询无法使用索引。

    SQL WHERE OR

    可以看出 mysql 按照最差情况处理。

    在软件开发或者检查数据库性能的过程中,如果无法确认 mysql 是否使用了索引,立即使用 explain 解释 SQL 语句,就能够看到本质。