zoukankan      html  css  js  c++  java
  • 20160924-2——mysql常见问题集锦

    一、数据类型相关问题

    1、varchar(N)占用多少空间

    (1)varchar(N)里的N是字符数,而不是字节数;

    (2)字符类型(varchar text blob等)空间=字符实际长度+字段长度;

    (3)varchar(N)占用的空间:
    如果是lantin1字符集时,最大空间=1N+(1 or 2)bytes,因为lantin1的1个字符占用1个字节,后面加的1或2bytes是用来表示字段长度的,当可能超过255个字节时,要2个bytes来表示字段长度;
    如果是utf8字符集时,最大空间=3
    N+(1 or 3)bytes,因为utf8的1个字符占用3个字节,后面加的1或2bytes是用来表示字段长度的,当可能超过255字节时,要2个bytes来表示字段长度;

    (4)当varchar(N)可能超过255字节时,字段长度=2;
    例子:varchar(100)字符集为utf8时,’aaaa‘分别占用几个字节?
    因为:varchar(100)如果存储中文的话,将占用3100 >225个字节,因此,需要2个字节来表示字段长度
    ’aaaa‘占用的空间=3
    100+2=302bytes

    (5)补充:

    2、char和varchar(N)类型的区别:

    (1)char最大可表示255个字符,而varchar的总长度不能超过65535个字节(bytes),最大支持字符数根据字符集不同而不同;
    (2)char会截掉尾部空字符串,而varchar不会截掉尾部空字符串;
    (3)”char固定长度,varchar变长“这种说法对于innodb存储引擎是错误的。
    innodb中是根据row_format来决定的:

    除了redundant外,在处理多字节字符集(gbk utf8等)char字段时,innodb都会当成变长字符来处理,单字节的则仍然分配固定长度空间;也就是说,utf8 gbk字符集的char和varchar一致,都是变长的,而lantin1字符集的char类型是固定长度的。
    【总结】
    使用innodb存储引擎,对于常用的字符集gbk或utf8,char和varchar没区别,建议用varhcar类型。

    3、varchar和text都可以表示长字符,且都是实际多少字符就占用多少空间,那需要存储多长字符串时,是选择varchar还是text呢?

    (1)功能方面:
    varchar长度有限制,所有char和varchar字段总和不能超过65535字节;且varchar可以有默认值。
    而text类型最长可以存储4G,且text不能设置默认值;
    【如何选择】
    a、如果表中有太多长字段,可能无法将所有字段都建为varchar(例如,所有字段建立为varhcar大于65535字节的时候),可以结合varchar和text使用。
    b、如果字段需要有默认值,那么使用varchar。

    (2)性能方面:
    有一种常见说法是“text字段是溢出字段,而varchar不会溢出,所以varchar更高效”======这种说法不完全对,因为text字段不一定溢出,只有当一整个行长度无法存入页中,可能会将最长的字段将余下行链接到其他页,这一点上text和varchar都是一样的 ;

    text字段无法利用tmp_table_size内存排序,直接磁盘排序(using filesort);

    3、存储字符串推荐选择使用varchar(N),N尽量小,为什么?

    varchar(10)和varchar(100)为例子:
    引擎层(磁盘存储与buffer pool)空间使用上两种是相同的;
    server层处理数据时并非按照实际大小分配内存。

    一些操作:排序、表DDL,varchar(100)会使用更多的磁盘和内存空间,效率会更低;
    因为:server层并不知道引擎层数据是怎么组织的,各引擎组织方式肯定不一样,server层在分配内存时使用的是表定义时的长度,一些需要在server处理数据的操作都可能会受到影响!比如排序、加索引时会使用更多内存(tmp_table_size)或磁盘空间,性能受到影响。

    4、int(11),给int指定长度有什么意义?

    int(11)和int(4)为例子:
    (1)int(11)和int(4)没有区别,只有在字段定义时加了zerofill属性,显示字段值时不足指定宽度会补0,但是zerofill几乎不用;
    【补充】
    默认int就是int(11),这是有符号的整型,如果是无符号整型则是int(10)

    ===========================================

    二、锁相关问题

    innodb存储引擎的锁按照锁粒度分:行级锁、表级锁两种;按照锁模式分:共享锁、独占锁;
    行级锁分为:记录锁、间隙锁(gap key)、next-key锁(记录锁+间隙锁);

    1、行级锁

    (1)概念:

    行级锁是MySQL中粒度最小的一种锁,他能大大减少数据库操作的冲突。但是粒度越小,实现的成本也越高。MYISAM引擎只支持表级锁,而INNODB引擎能够支持行级锁。

    (2)innodb的行级锁的三种类型:

    (2.1)记录锁:是加在索引记录上的;
    (2.2)间隙锁:gap lock,对索引记录间的范围加锁,或者加在最后一个索引记录的前面或后面;间隙锁主要是防止幻读,用在Repeated-read(简称RR)隔离级别下,在Read-commited(简称RC)下,一般没有间隙锁(外键情况下例外)。且间隙锁只会出现在辅助索引上,唯一索引和主键索引没有间隙锁。间隙锁(无论是S还是X)都只会阻塞insert操作。
    (2.3)Next-key锁:记录锁和间隙锁的组合,间隙锁锁定记录锁之前的范围;

    (3)行级锁又分为共享锁和排他锁

    INNODB的行级锁有共享锁(S LOCK)和排他锁(X LOCK)两种。共享锁允许多个线程读同一行记录,不允许任何线程对该行记录进行修改。排他锁允许当前线程删除或更新一行记录,其他线程不能操作该记录。

    (4)共享锁

    【用法】: SELECT … LOCK IN SHARE MODE; MySQL会对查询结果集中每行都添加共享锁。
    【 锁申请前提】:当前没有线程对该结果集中的任何行使用排他锁,否则申请会阻塞。
    【操作限制】:如下是,使用共享锁和不使用共享锁的线程,对锁定记录操作的限制

    (4.1) 使用共享锁线程可对其锁定记录进行读取,其他线程同样也可对锁定记录进行读取操作,并且这两个线程读取的数据都属于同一个版本。
    (4.2) 对于写入操作,使用共享锁的线程需要分情况讨论,当只有当前线程对指定记录使用共享锁时,线程是可对该记录进行写入操作(包括更新与删除),这是由于在写入操作前,线程向该记录申请了排他锁,然后才进行写入操作;当其他线程也对该记录使用共享锁时,则不可进行写入操作,系统会有报错提示。不对锁定记录使用共享锁的线程,当然是不可进行写入操作了,写入操作会阻塞。
    (4.3)使用共享锁进程可再次对锁定记录申请共享锁,系统并不报错,但是操作本身并没有太大意义。其他线程同样也可以对锁定记录申请共享锁。
    (4.4)使用共享锁进程可对其锁定记录申请排他锁;而其他进程是不可以对锁定记录申请排他锁,申请会阻塞。

    (5)排他锁:

    【用法】: SELECT … FOR UPDATE; MySQL会对查询结果集中每行都添加排他锁,在事物操作中,任何对记录的更新与删除操作会自动加上排他锁。
    【锁申请前提】:当前没有线程对该结果集中的任何行使用排他锁或共享锁,否则申请会阻塞。
    【操作限制】:如下是,使用排他锁和不使用排他锁的线程,对锁定记录操作限制

    (5.1)使用排他锁线程可以对其锁定记录进行读取,读取的内容为当前事物的最新版本;而对于不使用排他锁的线程,同样是可以进行读取操作,这种特性是一致性非锁定读。即对于同一条记录,数据库记录多个版本,在事物内的更新操作会反映到新版本中,而旧版本会提供给其他线程进行读取操作。
    (5.2)使用排他锁线程可对其锁定记录进行写入操作;对于不使用排他锁的线程,对锁定记录的写操作是不允许的,请求会阻塞。
    (5.3)使用排他锁进程可对其锁定记录申请共享锁,但是申请共享锁之后,线程并不会释放原先的排他锁,因此该记录对外表现出排他锁的性质;其他线程是不可对已锁定记录申请共享锁,请求会阻塞。
    (5.4)使用排他锁进程可对其锁定记录申请排他锁(实际上并没有任何意义);而其他进程是不可对锁定记录申请排他锁,申请会阻塞。

    (6)SQL里的锁使用情况举例

    (6.1)delete from t where id=10;该SQL会在主键索引(id=10)上加记录锁,锁住记录行,且是独占锁(意向独占锁,ix);
    (6.2)select * from t where id=10 lock in shared mode;该SQL会在主键索引(id=10)上加j记录锁,锁住记录,且是共享锁(意向共享锁,is);
    (6.3)insert into t values(12,1);该SQL会在insert前会加一个insert intention gap lock,这个锁是允许并发但是不允许id同值的并发。理解上就是:允许其他insert语句,但是不允再对id=12做并发插入;insert后在(12,1)上加记录锁。
    (6.4)RR模式下create table P as select * from T;这个SQL里会对表T加锁,加共享的Next-key lock,锁住T表所有记录以及间隙锁;
    (6.5)RC模式下create table P as select * from T;这个SQL不会对T表加锁,对T表做一致性读(快照读);

    2、表级锁

    (1)概念

    表级锁是MySQL中粒度最大的一种锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。

    (2)表级锁的类型

    表级锁定分为两类:读锁与写锁。读锁是预期将对数据表进行读取操作,锁定期间保证表不能被修改。写锁是预期会对数据表更新操作,锁定期间保证表不能被其他线程更新或读取。

    (3)读锁

    【用法】:LOCK TABLE table_name [ AS alias_name ] READ; 指定数据表,LOCK类型为READ即可,AS别名是可选参数,如果指定别名,使用时也要指定别名才行。
    【申请读锁前提】:当前没有线程对该数据表使用写锁,否则申请会阻塞。
    【操作限制】:其他线程可以对锁定表使用读锁;其他线程不可以对锁定表使用写锁

    对于使用读锁的MySQL线程,由于读锁不允许任何线程对锁定表进行修改,在释放锁资源前,该线程对表操作只能进行读操作,写操作时会提示非法操作。而 对于其他没使用锁的MySQL线程,对锁定表进行读操作可以正常进行,但是进行写操作时,线程会等待读锁的释放,当锁定表的所有读锁都释放时,线程才会响 应写操作。

    (4)写锁

    【用法】:LOCK TABLE table_name [AS alias_name] [ LOW_PRIORITY ] WRITE;别名用法与读锁一样,写锁增加了指定优先级的功能,加入LOW_PRIORITY可以指定写锁为低优先级。
    【申请写锁前提】:当没有线程对该数据表使用写锁与读锁,否则申请回阻塞。
    【操作限制】:其他MySQL线程不可以对锁表使用写锁、读锁

    对于使用写锁的MySQL线程,当前线程可以对锁定表进行读写操作。但是对于其他线程,对指定表读写操作都是非法的,需要等待直到写锁释放。

    (5)读锁和写锁的优先级关系

    对于锁分配的优先级是: LOW_PRIORITY WRITE < READ < WRITE
    (5.1)当多个线程申请锁,会优先分配给WRITE锁,不存在WRITE锁时,才分配READ锁,LOW_PRIORITY WRITE需要等到WRITE锁与READ都释放后,才有机会分配到资源。
    (5.2)对于相同优先级的锁申请,分配原则为谁先申请,谁先分配。

    (6)其他注意事项

    (6.1)不能操作(查询或更新)没有被锁定的表。
    例如:当只申请table1的读锁,SQL语句中包含对table2的操作是非法的:

    mysql> LOCK TABLE test READ;
    Query OK, 0 rows affected (0.00 sec)
    mysql> SELECT * FROM test_myisam;
    ERROR 1100 (HY000): Table 'test_myisam' was not locked with LOCK TABLES
    

    (6.2)不能在一个SQL中使用两次表(除非使用别名)
    当SQL语句中多次使用一张表时,系统会报错。例如:

    mysql> LOCK TABLE test READ;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SELECT * FROM test WHERE id IN (SELECT id FROM test );
    ERROR 1100 (HY000): Table 'test' was not locked with LOCK TABLES
     解决这个问题的方法是使用别名,如果多次使用到一个表,需要声明多个别名。
    
    mysql> LOCK TABLE test AS t1 READ, test AS t2 READ;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql>  SELECT * FROM test AS t1 WHERE id IN (SELECT id FROM test AS t2);
    

    (6.3)申请锁时使用别名,使用锁定表时必须加上别名。

    三、性能相关问题

    1、null在B+tree里是如何存储的?为什么推荐为每个字段设置默认值而不使用null??

    1)、不同数据处理null方式不同,但查询时任何数据库对于允许null值的字段处理比较复杂。
    2)、对于innodb类型的表来说,innodb记录(索引)头包含一个null位图,标志哪些字段是null,innodb中字段为null值可以存在索引中,索引中null值被当成一个值处理;oracle里则不会,oracle里索引中不会收录为null的值;
    3)、null无法做比较(< > !=等)可能出现“数据丢失”,只能is null来判断!
    例子:select …from …where a is null;这个sql在mysql里可以利用索引,oracle则不行;
    综合以上,推荐字段里设置默认值,而不使用null;

    2、外键约束,在相比与程序做的判断约束下,二者哪种性能更高?实际生产环境是否建议使用外键约束??

    现在的应用都讲究快速迭代,表结构变化很频繁,如果用了主外键会非常困难和麻烦。
    在进行线上加载或清理工作,如果用了主外键,数据维护会很麻烦;主外键有时候也并不能覆盖业务逻辑约束。
    综合考虑是:性能要求高,并发大,数据多的场景下,尽可能不要使用外键约束。类似的:主外键、触发器、函数、存储过程等都不推荐使用,尽量保持数据库“简单”。

    3、子查询 VS join

    1)子查询分为:非关联字查询(子查询里没有外表依赖)、关联子查询(子查询里有外表依赖)

    2)子查询写起来简单,可读性较好(子查询较少时),join要实现子查询功能比较难写
    例子:子查询改写成join
    (非关联子查询)select * from t1 where t1.a in (select t2.a from t2 where t2.b=1);
    等价于
    select t1.* from t1,(select distinct a from t2 where b=1) t3
    where t3.a=t1.a;

    (关联子查询)select * from t1 where t1.a in (select t2.a from t2 where t2.b=t1.b);
    等价于
    select t1.* from t1.(select distinct a from t2 where t2) t3 where t3.a=t1.a and t3.b=t1.b;
    子查询改写成join,其实join里还是有一个子查询的(作为临时表),而且改成join后SQL复杂很多。

    【总结】
    mysql使用next loop方式进行join;
    子查询中t1,t2无法正确使用索引;
    外表驱动子(查询)表,这里t1驱动t2表;要尽量使外表数据小,如果外表是大表则效率会非常差;
    对于不带子查询的join,mysql可以使用合理的索引和驱动表;
    mysql5.6之后,对子查询做了优化:子表只查一次,并将结果集存储在临时hash表,需要设置优化器参数optimizer_switch来优化;

    【建议】
    mysql的子查询效率通常比较低,能用(不带字查询的)join尽量使用join;否则建议将查询频率非常高的复杂查询拆分成多条简单查询,通过提升系统并发来达到优化目的。
    也说明:不带子查询的join性能才比子查询好,带了子查询的join效率依旧差。

    4、视图的性能

    视图仅推荐在做权限控制,非线上业务查询的时候使用。

    5、mysql极限

    1)mysql单表字段一般多少比较合适?超过多少会出现严重性能问题?
    innodb最大支持1000个字段,出现性能问题通常不是简单因为字段过多导致,与之相对通常是字段组合不合理导致。
    建议根据业务查询习惯指定最合理简练的表结构。

    2)mysql一个数据库多少张表比较合适?
    mysql会为每张表分配数据文件(独立表空间)与表定义文件;
    表过多时需要更多资源,需要调节部分参数(innodb_open_file open_file_limit table_open_cache等参数)
    mysql查询时都会开表以及关表。太多表可能导致操作变慢;
    建议按照业务模块拆分到不同实例;





  • 相关阅读:
    codeforces 616B Dinner with Emma
    codeforces 616A Comparing Two Long Integers
    codeforces 615C Running Track
    codeforces 612C Replace To Make Regular Bracket Sequence
    codeforces 612B HDD is Outdated Technology
    重写父类中的成员属性
    子类继承父类
    访问修饰符
    方法的参数
    实例化类
  • 原文地址:https://www.cnblogs.com/cjing2011/p/23da9b8bb7ea917da99d159cb15496e3.html
Copyright © 2011-2022 走看看