索引下推

SELECT * FROM employees WHERE name like ‘LiLei%’ AND age = 22 AND position =’manager’
辅助的联合索引(name,age,position),正常情况按照最左前缀原则。这种情况只会走name字段索引,因为根据name字段过滤完,得到的索引行里的age和 position是无序的,无法很好的利用索引。
在MySQL 5.6 之前的版本,这个查询只能在联合索引里匹配到名字是 ‘LiLei’ 开头的索引,然后拿这些索引对应的主键逐个回表,到主键索引上找出相应的记录,再比对age和position这两个字段的值是否符合。
MySQL 5.6 引入了索引下推优化,可以在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数。使用了索引下推优化后,上面那个查询在联合索引里匹配到名字是 ‘LiLei’ 开头的索引之后,同时还会在索引里过滤age和position这两个字段,拿着过滤完剩下的索引对应的主键id再回表查整行数据。
索引下推会减少回表次数,对于innodb引擎的表索引下推只能用于二级索引,innodb的主键索引(聚簇索引)树叶子节点上保存的是全行数据,所以这个时候索引下推并不会起到减少查询全行数据的效果。
为什么“>”不用索引,而“like ‘lilei%’”却可以用索引?
where like ‘lilei%’ 其实会用到了索引下推优化;
范围查找可能mysql认为过滤的结果集非常大,而like xx%过滤后的结果集比较小,所以选择给like使用索引下推。但这种情况不是绝对的,因为有时候like的结果集会比范围查找的结果集大,所以有时候like xx%也不一定会用到索引下推;
为什么范围查找Mysql没有用索引下推优化?
估计应该是Mysql认为范围查找过滤的结果集过大,like KK% 在绝大多数情况来看,过滤后的结果集比较小,所以这里Mysql选择给 like KK% 用了索引下推优化,当然这也不是绝对的,有时like KK% 也不一定就会走索引下推。

范围条件放最后

这一点,也是针对联合索引来说的,所有记录都是按照索引列的值从小到大的顺序排好序的,而联合索引则是按创建索引时的顺序进行分组排序。

尽量用覆盖索引

覆盖索引是非常有用的工具,能够极大地提高性能。考虑一下如果查询只需要扫描索引而无需回表,会带来多少好处。
索引条目通常远小于数据行大小,所以如果只需要读取索引,那MySQL就会极大地减少数据访问量。这对缓存的负载非常重要,因为这种情况下响应时间大部分花费在数据拷贝上。覆盖索引对于I/O密集型的应用也有帮助,因为索引比数据更小,更容易全部放入内存中。
因为索引是按照列值顺序存储的,所以对于I/O密集型的范围查询会比随机从磁盘读取每一行数据的I/O要少得多。由于InnoDB的聚簇索引,覆盖索引对InnoDB表特别有用。InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询。
尽量使用覆盖索引,只访问索引列和查询列一致,不是必要的情况下减少 select * ,除非是需要将表中的全部列检索后,进行缓存。

  1. explain select * from employees where name = 'Lucy' and age = 23;
  2. explain select id,age,position from employees where name = 'Lucy' and age = 23;

两条语句差异
image.png
image.png

使用 or 关键字

explain SELECT * FROM order_test WHERE order_no='DD00_6S' OR order_no='DD00_9S';

explain SELECT * FROM order_test WHERE expire_time='2022-03-22 18:35:09' OR order_note='abc';

image.png
如果使用 idx_expire_time 执行查询的话,对应的范围区间就是[第一条记录,最后一条记录],也就是需要将全部二级索引的记录进行回表,这个代价肯定比直接全表扫描都大了。也就是说一个使用到索引的搜索条件和没有使用该索引的搜索条件使用 OR 连接起来后是无法使用该索引的。道理很简单,idx_expire_time 这个二级索引的记录中不包含order_note这个字段,也就是说,即使二级索引 idx_expire_time 中找到了满足 expire_time= ‘2022-03-22 18:35:09’ 的记录,是无法判定 order_note 是否满足 order_note = ‘abc’ 的,又因为是 OR 条件,所以必须要在主键索引中从第一条记录到最后一条记录逐条判定 order_note 是否等于’abc’。 当然如果两个条件都是索引列,情况会有变化:explain SELECT FROM order_test WHERE expire_time=’2022-03-22 18:35:09’ OR order_no=’DD00_6S’;
image.png
如果将 explain SELECT
FROM order_test WHERE expire_time=’2022-03-22 18:35:09’ OR order_note=’abc’; 改为: explain SELECT FROM order_test WHERE expire_time=’2022-03- 22 18:35:09’ union all SELECT FROM order_test WHERE order_note=’abc’;
image.png从结果中可以看到使用到索引了,当然使用覆盖扫描也可以优化这个问题:explain SELECT order_status,id FROM order_test_cut WHERE insert_time=’2022-03-22 18:34:55’ or expire_time=’2022-03-22 18:28:28’;
image.png

Like查询

like 以通配符开头 ‘%abc…’ ,mysql 索引失效会变成全表扫描的操作: explain SELECT FROM order_test WHERE order_no LIKE ‘%_6S’ ;
image.png
此时如果使用覆盖索引可以改善这个问题。
image.png
有些搜索条件中虽然出现了索引列,但却不能使用到索引,比如下边这个查询:explain SELECT
FROM test1 WHERE order_no > ‘z’ AND order_no LIKE ‘%a’ ;
image.png
其中的 order_no> ‘z’ 可以使用到索引,但是 order_no LIKE ‘%a’ 却无法使用到索引,在以前版本的 MySQL 中,是按照下边步骤来执行这个查询的:
1. 先根据 order_no> ‘z’ 这个条件,从二级索引 idx_order_no 中获取到对应的二级索引记录;
2. 根据上一步骤得到的二级索引记录中的主键值进行回表找到完整的用户记录再检测该记录是否符合 LIKE ‘%a’ 这个条件,将符合条件的记录加入到最后的结果集;
但是 order_no LIKE ‘%a’ 不能组成范围区间参与 range 访问方法的执行,这个条件毕竟只涉及到了 order_no 列,MySQL 把上边的步骤改进了一下:
1. 先根据 order_no> ‘z’ 这个条件,定位到二级索引 idx_order_no 中对应的二级索引记录;
2. 对于指定的二级索引记录,先不着急回表,而是先检测一下该记录是否满足 order_no LIKE ‘%a’ 这个条件,如果这个条件不满足,则该二级索引记录就没必要回表;
3. 对于满足 order_no LIKE ‘%a’ 这个条件的二级索引记录执行回表操作;
回表操作其实是一个随机 IO,比较耗时,所以上述修改可以省去很多回表操作的成本。这个改进称之为:索引条件下推(英文名:ICP ,Index Condition Pushdown)

扫描区间

其实对于B+Tree索引来说,只要索引列和常数使用=、<=>、IN、NOT IN、IS NULL、IS NOT NULL、>、<、>=、<=、BETWEEN、!=(不等于也可以写成<>)或者LIKE操作符连接起来,就可以产生一个区间。
1. IN 操作符的效果和若干个等值匹配操作符 = 之间用 OR 连接起来是一样的,也就是说会产生多个单 点区间,比如:下边这两个语句的效果是一样的:
SELECT FROM order_test WHERE insert_time IN (2022-03-22 18:23:42, 2022-03-29 18:23:42); 和 SELECT FROM order_test WHERE insert_time= 2022-03-22 18:23:42 OR insert_time = 2022-03-22 18:23:42;
2. != 产生的扫描区间,比如: SELECT FROM order_test WHERE order_no != ‘DD00_9S’ 此 时使用 idx_expire_time 执行查询时对应的扫描区间就是[第一条记录,’DD00_9S’][ ‘DD00_9S’, 最后一条记录]
3. LIKE 操作符比较特殊,只有在匹配完整的字符串或者匹配字符串前缀时才产生合适的扫描区间。 对于某个索引列来说,字符串前缀相同的记录在由记录组成的单向链表中肯定是相邻的。比如:有 一个搜索条件是 note LIKE ‘b%’ ,对于二级索引 idx_note 来说,所有字符串前缀为 ‘b’ 的二 级索引记录肯定是相邻的。这也就意味着只要定位到 idx_note 值的字符串前缀为 ‘b’ 的第一条 记录,就可以沿着记录所在的单向链表向后扫描,直到某条二级索引记录的字符串前缀不为 b 为 止。很显然, note LIKE’ b%’ 形成的扫描区间相当于* [‘b’, ‘c’)

使用索引扫描来做排序和分组

MySQL有两种方式可以生成有序的结果:通过排序操作或者按索引顺序扫描,如果EXPLAIN出来的type列的值为index,则说明MySQL使用了索引扫描来做排序。
扫描索引本身是很快的,因为只需要从一条索引记录移动到紧挨着的下一条记录。但如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就都回表查询一次对应的行。这基本上都是随机I/O,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢,尤其是在IO密集型的工作负载时。
MySQL可以使用同一个索引既满足排序,又用于查找行。因此,如果可能设计索引时应该尽可能地同时满足以下条件:

  • 只有当索引的列顺序和 ORDER BY 子句的顺序完全一致,并且所有列的排序方向都一样时,MySQL才能够使用索引来对结果做排序。
  • 如果查询需要关联多张表,则只有当 ORDER BY 子句引用的字段全部为第一个表时,才能使用索引做排序。

有一些情况下对结果集中的记录进行排序是可以使用到索引的。

EXPLAIN SELECT * FROM test1 ORDER BY order_no LIMIT 10;

这个查询语句可以利用idx_order_no索引直接取出order_no列的10条记录,然后再进行回表操作。但是很多情况下排序操作无法使用到索引,只能在内存中或者磁盘中进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序。如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的Extra列中显示Using filesort。

EXPLAIN SELECT * FROM test1 ORDER BY order_note LIMIT 10;

GROUP BY的执行计划的Extra包含Using filesort提示,可是查询语句中明明没有写ORDER BY子句,这是因为MySQL会在包含GROUP BY子句的查询中默认添加上 ORDER BY子句。

EXPLAIN SELECT order_note,COUNT(*) AS amount FROM test1 GROUP BY order_note;

如果不想为包含GROUP BY子句的查询进行排序,需要显示的写上ORDER BY NULL。

EXPLAIN SELECT order_note,COUNT(*) AS amount FROM test1 GROUP BY order_note ORDER BY NULL;

分页查询

当执⾏分⻚查询时,例如 limit 10000,10,可以查询从10001到10010 的 10条数据,但是在MySQL的查询中,是直接查询了从1-10010条数据,然后丢弃了前10000条,只保留了后10条,那么当数据量越拉越⼤的时候,分⻚查询肯定会很慢。

select * from employees limit 10000,10;

image.png
自增且连续的主键

如果主键是⾃增且连续的,那么可以使⽤下⾯的优化⽅式查询。可以有效的利⽤主键索引,查询到需要的内容,但是如果其中有些数据被删除掉,导致主键不连续,那么可能会造成不同的查询⽅式查询出的结果不⼀致。

select * from employees where id > 90000 limit 5;

image.png
例如删除掉了部分数据,两种查询⽅式的结果会有差异。
image.png
所以在使⽤这种⽅式优化分⻚查询时,可以动态的传⼊id参数,例如每次查询5条数据,那么第⼀次查询的5条数据中的最后⼀条数据的id作为下次查询的条件,查询⽐这个id⼤的后5条记录。

非主键字段排序分页

在employees表中,存在⼀个联合索引name,age,position,使⽤联合索引的第⼀个字段进⾏排序,然后分⻚查询。

select * from employees ORDER BY name limit 90000,5;

image.png
根据执⾏计划来看,并没有使⽤联合索引,⽽且⽤到了using filesort,原因可能是因为查询的数据量过⼤,导致使⽤索引的成本⽐全表扫描的成本⾼,其实关键是让排序时返回的字段尽可能少,所以可以让排序和分⻚操作先查出主键,然后根据主键查到对应的记录。

select * from employees e inner join (select id from employees order by name limit 90000,5) ed on e.id = ed.id;

image.png
⾸先执⾏的是id为2的,也就是⼦查询ed表,使⽤了联合索引,之后id相同的循序执⾏,查询衍⽣表2,也就是ed表的结果,因为ed表的结果只有5条,所以使⽤全表扫描,查询e表使⽤了主键索引,所以整体来看,基本都使⽤到了索引,⽽且排序是usingindex在索引树进⾏排序。

Join关联查询

⾸先创建两个示例表,t1表和t2表的结构完全相同,都有⼀个索引a字段,区别是t1表中有10000条数据,⽽t2表中有100条数据。

CREATE TABLE `t1` ( 
  `id` int(11) NOT NULL AUTO_INCREMENT, 
  `a` int(11) DEFAULT NULL, 
  `b` int(11) DEFAULT NULL, 
  PRIMARY KEY (`id`), 
  KEY `idx_a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

create table t2 like t1;

‐‐ 插⼊⼀些示例数据 
‐‐ 往t1表插⼊1万⾏记录
drop procedure if exists insert_t1;
delimiter ;;
create procedure insert_t1()
begin
declare i int;
set i=1;
while(i<=10000) do 
insert into t1(a,b) values(i,i);
set i=i+1;
end while;
end;;
delimiter ;

call insert_t1();

‐‐ 往t2表插⼊100⾏记录
drop procedure if exists insert_t2;
delimiter ;;
create procedure insert_t2()
begin
declare i int;
set i=1;
while(i<=100) do 
insert into t2(a,b) values(i,i);
set i=i+1;
end while;
end;;
delimiter ;
call insert_t2();

嵌套循环连接Nested-LoopJoin(NLJ)算法

⼀次⼀⾏循环地从第⼀张表(称为驱动表)中读取⾏,在这⾏数据中取到关联字段,根据关联字段在另⼀张表(被驱动表)⾥取出满⾜条件的⾏,然后取出两张表的结果合集。

EXPLAIN select * from t1 inner join t2 on t1.a= t2.a;

image.png
当执⾏连表查询时,如果关联字段是索引字段,那么MySQL会使⽤NLJ算法,⾸先根据过滤条件过滤出结果数据的多少来判断是否是驱动表,示例sql没有额外的where过滤条件,就以实际数据来计算,t2表中包含100条数据,⽽t1表中包含10000条数据,所以t2表作为⼩表(驱动表)先进⾏查询,可以根据执⾏计划来判断,id相同会顺序执⾏,所以是t2表先执⾏,此时会进⾏磁盘扫描100次,读取出100条记录,由于是索引关联,所以t2表中的每条记录都可以根据索引快速找到t1表中关联的数据⾏,结果合并后返回给客户端,即t2表的每条数据会在t1表中磁盘扫描1次得到结果,所以共计磁盘扫描200次。
在使⽤inner join时,排在前⾯的表不⼀定是驱动表,⽽left join和right join不同。当使⽤left join时,左表是驱动表,右表是被驱动表,当使⽤right join时,右表时驱动表,左表是被驱动表,当使⽤join时,mysql会选择数据量⽐较⼩的表作为驱动表,⼤表作为被驱动表。
使⽤了NLJ算法。⼀般join语句中,如果执⾏计划Extra中未出现Using join buffer则表示使⽤的join算法是NLJ。

基于块的嵌套循环连接BlockNested-LoopJoin(BNL)算法

把驱动表的数据读⼊到 join_buffer 中,然后扫描被驱动表,把被驱动表每⼀⾏取出来跟 join_buffer 中的数据做对⽐。

EXPLAIN select * from t1 inner join t2 on t1.b= t2.b;

image.png
b字段是没有索引的,所以使⽤b字段关联的话会使⽤BNL算法,⾸先会把t2的数据存⼊join_buffer中,再从t1中取出每⼀⾏,与join_buffer中的t2的每⼀⾏做对⽐,满⾜则返回,继续下⼀条数据的⽐较。
整个过程对表t1和t2都做了⼀次全表扫描,因此扫描的总⾏数为10000(表t1的数据总量)+100(表t2的数据总量)=10100。并且join_buffer⾥的数据是⽆序的,因此对表t1中的每⼀⾏,都要做100次判断,所以内存中的判断次数是10010000=100万次。
join_buffer的⼤⼩是由参数join_buffer_size设定的,默认值是256k。如果放不下表t2的所有数据话,策略很简单,就是分段放,先放⼊t2表的部分数据,对⽐完成后,清空掉join_buffer,再读取剩下的数据,这样会导致t1表扫描次数的增加。
因此MySQL对于被驱动表的关联字段没索引的关联查询,⼀般都会使⽤BNL算法。如果有索引⼀般选择NLJ算法,有索引的情况下NLJ算法⽐BNL算法性能更⾼。
如果没有索引,⽽使⽤NLJ算法,那么扫描⾏数为100
10000=100万次,这个是磁盘扫描,⽤BNL磁盘扫描次数少很多,相⽐于磁盘扫描,BNL的内存计算会快得多。

straight_join

尽量关联字段加索引,让mysql做join操作时尽量选择NLJ算法,写多表连接sql时如果明确知道哪张表是⼩表可以⽤straight_join写法固定连接驱动⽅式,省去mysql优化器⾃⼰判断的时间。
straight_join功能同join类似,但能让左边的表来驱动右边的表,能改表优化器对于联表查询的执⾏顺序。
straight_join只适⽤于inner join,并不适⽤于left join,right join。(因为left join,right join已经代表指定了表的执⾏顺序)。
尽可能让优化器去判断,因为⼤部分情况下mysql优化器是⽐⼈要聪明的。使⽤straight_join⼀定要慎重,因为部分情况下⼈为指定的执⾏顺序并不⼀定会⽐优化引擎要靠谱。

大小表的定义

在决定哪个表做驱动表的时候,应该是两个表按照各⾃的条件过滤,过滤完成之后,计算参与join的各个字段的总数据量,⽽不是单纯的只看表数据量,数据量⼩的那个表,就是“⼩表”,应该作为驱动表。

in和exists

同样是⼩表驱动⼤表,即⼩的数据集驱动⼤的数据集,当B表的数据集⼩于A表的数据集时,in优于exists,in条件会先执⾏。

select * from A where id in (select id from B)
# 等价于:
for (select id from B) {
  select * from A where A.id= B.id
}

当A表的数据集⼩于B表的数据集时,exists优于in将主查询A的数据,放到⼦查询B中做条件验证,根据验证结果(true或false)来决定主查询的数据是否保留。

# A表与B表的ID字段应建⽴索引
select * from A where exists (select 1 from B where B.id= A.id)
# 等价于:
for (select * from A) {
  select * from B where B.id= A.id
}

EXISTS(subquery)只返回TRUE或FALSE,因此⼦查询中的SELECT*也可以⽤SELECT1替换,官⽅说法是实际执⾏时会忽略SELECT清单,因此没有区别。
EXISTS⼦查询的实际执⾏过程可能经过了优化⽽不是我们理解上的逐条对⽐。
EXISTS⼦查询往往也可以⽤JOIN来代替,何种最优需要具体问题具体分析。

count()

‐‐ 临时关闭mysql查询缓存,为了查看sql多次执⾏的真实时间
set global query_cache_size=0;
set global query_cache_type=0;

EXPLAIN select count(1) from employees;
EXPLAIN select count(id) from employees;
EXPLAIN select count(name) from employees;
EXPLAIN select count(*) from employee;

以上4条sql中只有根据某个字段count不会统计字段为null值的数据⾏(count(id),count(name))。

效率比较

字段有索引:count()≈count(1)>count(字段)>count(主键id)。
字段有索引,count(字段)统计⾛⼆级索引,⼆级索引存储数据⽐主键索引少,所以count(字段)>count(主键id)。
字段⽆索引:count(
)≈count(1)>count(主键id)>count(字段)。
字段没有索引count(字段)统计⾛不了索引,count(主键id)还可以⾛主键索引,所以count(主键id)>count(字段)。
count(1)跟count(字段)执⾏过程类似,不过count(1)不需要取出字段统计,就⽤常量1做统计,count(字段)还需要取出字段,所以理论上count(1)⽐count(字段)会快⼀点。
count()是例外,mysql并不会把全部字段取出来,⽽是专⻔做了优化,不取值,按⾏累加,效率很⾼,所以不需要⽤count(列名)或count(常量)来替代count()。
对于count(id),mysql最终选择辅助索引⽽不是主键聚集索引,因为⼆级索引相对主键索引存储数据更少,检索性能应该更⾼,mysql内部做了点优化(应该是在5.7版本才优化)。

常见优化方法

自己维护的总行数
对于myisam存储引擎的表做不带where条件的count查询性能是很⾼的,因为myisam存储引擎的表的总⾏数会被mysql存储在磁盘上,查询不需要计算。
对于innodb存储引擎的表mysql不会存储表的总记录⾏数(因为有MVCC机制,不同事物读取的内容不同),查询count需要实时计算。
show table status
如果只需要知道表总⾏数的估计值可以⽤如下sql查询,性能很⾼。

show table status like 'employees';

Redis
插⼊或删除表数据⾏的时候同时维护redis⾥的表总⾏数key的计数值(⽤incr或decr命令),但是这种⽅式可能不准,很难保证表操作和redis操作的事务⼀致性。
计数表
插⼊或删除表数据⾏的时候同时维护计数表,让他们在同⼀个事务⾥操作。

索引设计原则

  1. 代码先行,索引后上

不能建完表马上就建立索引,一般应该等到主体业务功能开发完毕,把涉及到该表相关sql都要拿出来分析之后再建立索引。

  1. 联合索引尽量覆盖条件

比如可以设计一个或者两三个联合索引(尽量少建单值索引),让每一个联合索引都尽量去包含sql语句里的where、order by、group by的字段,还要确保这些联合索引的字段顺序尽量满足sql查询的最左前缀原则。
3. 不要在小基数字段上建立索引
如性别字段,其值不是男就是女,毫无意义(区分度不高)

  1. 长字符串我们可以采用前缀索引

KEY index (name(20),age,position)。

  1. where与order by冲突时优先where
  2. 基于慢sql查询做优化(依据B+Tree索引)

“set global long_query_time = 4” 设置慢SQL查询时间;
“set global slow_query_log = 1” 开启慢SQL查询日志;

更小的通常更好

一般情况下,应该尽量使用可以正确存储数据的最小数据类型。更小的数据类型通常更快,因为它们占用更少的磁盘、内存和CPU缓存,并且处理时需要的CPU周期也更少。
但是要确保没有低估需要存储的值的范围,因为在多个地方增加数据类型的范围是一个非常耗时和痛苦的操作。如果无法确定哪个数据类型是最好的。就选择不会超过范围的最小类型。

简单就好

简单数据类型的操作通常需要更少的CPU周期。例如:整型比字符操作代价更低,因为字符集和校对规则、排序规则使字符比较比整型比较更复杂。比如应该使用MySQL内建的类型而不是字符串来存储日期和时间。

尽量避免NULL

很多表都包含可为NULL的列,即使应用程序并不需要保存也是NULL,这是因为NULL是列的默认属性。通常情况下最好指定列为NOT NULL,除非真的需要存储NULL值。
如果查询中包含可为NULL的列,对MySQL来说更难优化,因为NULL的列使得索引、索引统计和值比较都更复杂。NULL的列会使用更多的存储空间,在MySQL里也需要特殊处理。当可为NULL的列被索引时,每个索引记录需要一个额外的字节。

整数类型

存储整数,可以使用这几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。分别使用8,16,24,32,64位存储空间,也就是1、2、3、4、8个字节。
同时整数类型有可选的 UNSIGNED 属性,表示不允许负值,这大致可以使正数的上限提高一倍。例如:TINYINT UNSIGNED 可以存储的范围是 0~255,而 TINYINT 的存储范围是 -128~127。
有符号和无符号类型使用相同的存储空间,并具有相同的性能,因此可以根据实际情况选择合适的类型。
MySQL可以为整数类型指定宽度,例如:INT(11),对大多数应用这是没有意义的,它不会限制值的合法范围,只是规定了MySQL的一些交互工具,如:HeidiSQL、Navicat等客户端工具,用来显示字符的个数。对于存储和计算来说,INT(1)和INT(20)是相同的。
在选择上,遵循着更小的通常更好的原则,在业务许可的情况下,尽量选择位数小的。

实数类型

实数是带有小数部分的数字。MySQL既支持精确类型的存储DECIMAL类型,也支持不精确类型存储FLOAT 和 DOUBLE 类型。
DECIMAL类型用于存储精确的小数,本质上MySQL是以字符串形式存放的,所以CPU不支持对DECIMAL的直接计算,所以在MySQL中自身实现了DECIMAL的高精度计算。相对而言,CPU直接支持原生浮点计算,所以浮点运算明显更快。浮点和 DECIMAL 类型都可以指定精度。对于 DECIMAL 列,可以指定小数点前后所允许的最大位数。这会影响列的空间消耗。MySQL 5.0 和更高版本将数字打包保存到一个二进制字符串中,每4个字节存9个数字。例如:DECIMAL(18,9)小数点两边将各存储9个数字,一共使用9个字节:小数点前的数字用4个字节,小数点后的数字用4个字节,小数点本身占1个字节。
MySQL 5.0 以后的版本中 DECIMAL 类型允许最多 65 个数字。浮点类型在存储同样范围的值时,通常比 DECIMAL 使用更少的空间。FLOAT 使用 4 个字节存储,DOUBLE占用8个字节,所以 DOUBLE 比 FLOAT有更高的精度和更大的范围。
因为需要额外的空间和计算开销,所以应该尽量只在对小数进行精确计算时才使用 DECIMAL,例如:存储财务或金融数据,在精度不敏感和需要快速运算的时候,选择 FLOAT 和 DOUBLE。
但是在数据量比较大的而且要求精度时,可以考虑使用 BIGINT 代替 DECIMAL,将需要存储的货币单位根据小数的位数乘以相应的倍数即可。假设要存储财务数据精确到万分之一分,则可以把所有金额乘以一百万,然后将结果存储在 BIGINT 里,这样可以同时避免浮点存储计算不精确和 DECIMAL 精确计算代价高的问题。

字符串类型

MySQL支持多种字符串类型,包括:VARCHAR和CHAR类型、BLOB和TEXT类型、ENUM和SET类型。
VARCHAR和CHAR类型
VARCHAR和CHAR是两种最主要的字符串类型。
VARCHAR:类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型更节省空间,因为它仅使用必要的空间,如:越短的字符串使用越少的空间。在内部实现上,既然是变长,VARCHAR需要使用 1 或 2个额外字节记录字符串的长度,如果列的最大长度小于或等于255字节,则只使用1个字节表示,否则使用2个字节。VARCHAR节省了存储空间,所以对性能也有帮助。但是,由于行是变长的,在UPDATE时新值比旧值长时,使行变得比原来更长,这就可能导致需要做额外的工作。如果一个行占用的空间增长,并且在页内没有更多的空间可以存储,在这种情况下,MyISAM会将行拆分成不同的片段存储,InnoDB则需要分裂页来使行可以放进页内。
CHAR:类型是定长的,MySQL总是根据定义的字符串长度分配足够的空间。当存储CHAR值时,MySQL会删除所有的末尾空格,CHAR 值会根据需要采用空格进行填充以方便比较。CHAR适合存储很短的字符串,或者所有值定长或都接近同一个长度。例如:CHAR非常适合存储密码的MD5值,因为这是一个定长的值。对于经常变更的数据,CHAR 也比 VARCHAR更好,因为定长的CHAR类型不容易产生碎片。对于非常短的列,CHAR 比 VARCHAR在存储空间上也更有效率。例如:用CHAR(1)来存储只有Y 和 N 的值,如果采用单字节字符集只需要一个字节,但是 VARCHAR(1) 却需要两个字节,因为还有一个记录长度的额外字节。

问题一:常用的Order by与Group by如何优化

a)如果order by的条件不在索引列上,就会产生Using filesort,尽量对order by的字段建立索引
b)能用覆盖索引尽量用覆盖索引
c)对于group by的优化如果不需要排序的可以加上order by null禁止排序

问题二:多线程同步和互斥有几种实现方法,都是什么?

线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。 线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。

线程间的同步方法大体可分为两类:用户模式和内核模式。
顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。 用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。

问题三:索引下推是怎么回事?

在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数

问题四:乐观锁,悲观锁适合什么场景?

悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升