MySQL的基本架构示意图:
大体来说,MySQL可以分为server层和存储引擎层两部分。
① server层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖MySQL的大多数核心服务功能
② 存储引擎层:存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎
连接器:连接器负责跟客户端建立连接、获取权限、维持和管理连接。
查询缓存:连接建立完成后,你就可以执行select语句了,此时会先进行查询缓存(缓存是key-value格式;key是sql语句,value是sql语句的查询结果)。
分析器:
1、词法分析: MySQL需要识别出里面的字符串分别是什么,代表什么。
2、语法分析:根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个SQL语句是否满足MySQL语法。
优化器:优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。
执行器:调用存储引擎接口,执行sql语句,得到结果
MySQL提供了插件式的存储引擎架构。所以MySQL存在多种存储引擎,可以根据需要使用相应引擎,或者编写存储引擎。存储引擎是基于表的,而不是基于库的。所以存储引擎也可被称为表类型。MySQL5.0支持的存储引擎包含 : InnoDB 、MyISAM 、BDB、MEMORY、MERGE、EXAMPLE、NDB Cluster、ARCHIVE、CSV、BLACKHOLE、FEDERATED 等,其中InnoDB和BDB提供事务安全表,其他存储引擎是非事务安全表。
1、InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
2、InnoDB支持外键,而MyISAM不支持。
3、InnoDB是支持表锁和行级锁,MyISAM只支持表锁
如果没有特别的需求,使用默认的 Innodb 即可。
MyISAM:以读为主的应用程序,比如博客系统、新闻门户网站。
Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。
show engines; 查看MySQL提供的所有存储引擎
创建新表时如果不指定存储引擎,那么系统就会使用默认的存储引擎,MySQL5.5之前的默认存储引擎是MyISAM,5.5之后就改为了InnoDB。
show variables like '%storage_engine%'; 查看mysql默认的存储引擎
show table status like "table_name"\G 查看表的存储引擎
MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护者满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。如下面的示意图所示 :
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找快速获取到相应数据。一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。索引是数据库中用来提高性能的最常用的工具。
导入资料中提供的sql脚本文件,已经准备了1000W条数据。
A. 根据ID查询
select * from tb_sku where id = 1999\G ;
查询速度很快, 接近0s , 主要的原因是因为id为主键, 有索引;
查看执行计划:
B. 根据 name进行精确查询
select * from tb_sku where name = '华为Meta1999'\G ;
查询速度太慢了,几乎使用了9s才完成数据的查询。
查看执行计划:
聚簇索引:将数据和索引放在一起,并且是按照一定顺序组织的,找到索引也就找到了数据。一般情况下主键就是默认的聚簇索引。
优缺点:使用聚簇索引进行行数据查询效率较高,更新数据的效率较低,同时会占用的存储空间较大。
非聚簇索引:叶子结点不存储数据,存储的是行的物理地址,在进行行数据查询的时候,需要根据物理地址值从数据库表中再次进行查询【回表】
优缺点:使用非聚簇索引进行行数据查询效率较低,更新数据的效率较高,同时占用的存储空间较小。
为了提升上述查询效率,可以对name字段创建索引。创建索引有两种方式:
1、方式一:在创建表的时候创建索引
-- 语法结构 CREATE TABLE 表名( 属性名 数据类型[完整性约束条件], 属性名 数据类型[完整性约束条件], ...... 属性名 数据类型 [ UNIQUE | FULLTEXT | SPATIAL ] INDEX | KEY [ 别名] ( 属性名1 [(长度)] [ ASC | DESC] ) );
示例:
-- 示例代码 CREATE TABLE `index1` ( `id` int(11) DEFAULT NULL, `name` varchar(20) DEFAULT NULL, `sex` tinyint(1) DEFAULT NULL, KEY `index1_id` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
2、方式二:使用create index语句进行索引创建
语法:
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name [USING index_type] ON tbl_name(index_col_name,...) // 如果指定的列的名称是多个,那么这个索引我们将其称之为复合索引
示例:
create index idx_name on tb_sku(name) ;
再次进行查询:
通过explain , 查看执行计划,执行SQL时使用了刚才创建的索引
1、UNIQUE:唯一索引
表示唯一的,不允许重复的索引,如果该字段信息保证不会重复例如身份证号用作索引时,可设置为UNIQUE。
2、FULLTEXT: 全文索引
表示全文搜索,在检索长文本的时候,效果最好,短文本建议使用普通索引,但是在检索的时候数据量比较大的时候,现将数据放入一个没有全局索引的表中,然后在用Create Index创建的Full Text索引,要比先为一张表建立Full Text然后在写入数据要快的很多。FULLTEXT 用于搜索很长一篇文章的时候,效果最好。用在比较短的文本,如果就一两行字的,普通的 INDEX 也可以。
3、SPATIAL: 空间索引
空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。
MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建。如果没有指定索引约束,此时创建的索引就是普通索引。而一般情况下只需要创建普通索引。
4、普通索引:如果没有指定索引约束,此时创建的索引就是普通索引。而一般情况下只需要创建普通索引。
索引是在MySQL的存储引擎层中实现的,而不是在服务器层实现的。所以每种存储引擎的索引都不一定完全相同,也不是所有的存储引擎都支持所有的索引类型的。
MySQL目前提供了以下4种索引:
各种存储引擎对索引的支持:
索引 | InnoDB引擎 | MyISAM引擎 | Memory引擎 |
---|---|---|---|
BTREE索引 | 支持 | 支持 | 支持 |
HASH 索引 | 不支持 | 不支持 | 支持 |
R-tree 索引 | 不支持 | 支持 | 不支持 |
Full-text | 5.6版本之后支持 | 支持 | 不支持 |
我们平常所说的索引,如果没有特别指明,都是指B+树(多路搜索树,并不一定是二叉的)结构组织的索引。
语法:
show index from table_name;
示例:查看tb_sku表中的索引信息;
show index from tb_sku ;
注意:主键自动创建索引
唯一索引不一定比普通索引快, 还可能慢。
1、查询时, 在未使用 limit 1 的情况下, 在匹配到一条数据后, 唯一索引即返回, 普通索引会继续匹配下一条数据, 发现不匹配后返回. 如此看来唯一索引少了一次匹配, 但实际上这个消耗微乎其微。
2、更新时, 这个情况就比较复杂了. 普通索引将记录放到 change buffer 中语句就执行完毕了。而对唯一索引而言, 它必须要校验唯一性, 因此, 必须将数据页读入内存确定没有冲突, 然后才能继续操作。
对于写多读少的情况 , 普通索引利用 change buffer 有效减少了对磁盘的访问次数, 因此普通索引性能要高于唯一索引.
1、优点
2、缺点
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行 INSERT、UPDATE 和DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。
环境准备
建表语句:
create table `tb_seller` ( `sellerid` varchar (100), `name` varchar (100), `nickname` varchar (50), `password` varchar (60), `status` varchar (1), `address` varchar (100), `createtime` datetime, primary key(`sellerid`) )engine=innodb default charset=utf8mb4;
初始化数据sql:
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('alibaba','阿里巴巴','阿里小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('baidu','百度科技有限公司','百度小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('huawei','华为科技有限公司','华为小店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itcast','传智播客教育科技有限公司','传智播客','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itheima','黑马程序员','黑马程序员','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('luoji','罗技科技有限公司','罗技小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('oppo','OPPO科技有限公司','OPPO官方旗舰店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('ourpalm','掌趣科技股份有限公司','掌趣小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('qiandu','千度科技','千度小店','e10adc3949ba59abbe56e057f20f883e','2','北京市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('sina','新浪科技有限公司','新浪官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('xiaomi','小米科技','小米官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','西安市','2088-01-01 12:00:00'); insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('yijia','宜家家居','宜家家居旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
创建索引:
create index idx_seller_name_sta_addr on tb_seller(name,status,address);
全职匹配查询:对索引中所有列都指定具体值。该情况下,索引生效,执行效率高。
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
① 违背了最左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。
匹配最左前缀法则,走索引:
违法最左前缀法则 , 索引失效:
![[MySQL面试题.png]]
如果符合最左法则,但是出现跳跃某一列,只有最左列索引生效:
② 范围查询: 范围查询右边的列,不能使用索引 。
根据前面的两个字段name , status 查询是走索引的, 但是最后一个条件address 没有用到索引。
>= 不走索引 > 走索引
③ 列运算:不要在索引列上进行运算操作, 索引将失效。
④ 字符串:字符串不加单引号,造成索引失效。
由于,在查询时没有对字符串加单引号,MySQL的查询优化器,会自动的进行类型转换,造成索引失效。
⑤ 模糊查询:以%开头的like模糊查询,索引失效。如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
解决方案 :
通过覆盖索引来解决
① 建立索引的时候一般要考虑到字段的使用频率,经常作为条件进行查询的字段比较适合。
② 如果需要建立联合索引的话,还需要考虑联合索引中的顺序。
③ 此外也要考虑其他方面,比如防止过多的索引对表造成太大的压力
MySQL提供了explain命令来查看语句的执行计划,MySQL在执行某个语句之前,会将该语句过一遍查询优化器,之后会拿到对语句的分析,也就是执行计划,其中包含了许多信息. 可以通过其中和索引有关的信息来分析是否命中了索引,例如possilbe_key,key,key_len等字段,分别说明了此语句可能会使用的索引,实际使用的索引以及使用的索引长度.
在业务系统中,除了使用主键进行的查询,其他的我都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。
慢查询的优化首先要搞明白慢的原因是什么?
① 是查询条件没有命中索引?
② 是load了不需要的数据列?
③ 还是数据量太大?
所以优化也是针对这三个方向来的:
1、分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
2、分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
3、如果是表中的数据量是否太大导致查询慢,可以进行横向或者纵向的分表.
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10S以上的语句。默认情况下,Mysql数据库并不启动慢查询日志,需要我们手动来设置这个参数,当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件,也支持将日志记录写入数据库表。
慢查询的配置:
# 是否开启慢查询日志,1表示开启,0表示关闭 slow_query_log=1 # 旧版(5.6以下版本)MySQL数据库慢查询日志存储路径。可以不设置该参数,系统则会默认给一个缺省的文件host_name-slow.log log_slow_queries=/var/lib/mysql/mysql_slow.log # 新版(5.6及以上版本)MySQL数据库慢查询日志存储路径。可以不设置该参数,系统则会默认给一个缺省的文件host_name-slow.log slow_query_log_file=/var/lib/mysql/mysql_slow.log # 慢查询阈值,当查询时间大于设定的阈值时,记录日志。 long_query_time = 1 # 未使用索引的查询也被记录到慢查询日志中(可选项)。 log_queries_not_using_indexes=0 # 日志存储方式。log_output='FILE'表示将日志存入文件,默认值是'FILE'。log_output='TABLE'表示将日志存入数据库,这样日志信息就会被写入到mysql.slow_log表中。MySQL数据库支持同时两种日志存储方式,配置的时候以逗号隔开即可,如:log_output='FILE,TABLE'。日志记录到系统的专用日志表中,要比记录到文件耗费更多的系统资源,因此对于需要启用慢查询日志,又需要能够获得更高的系统性能,那么建议优先记录到文件。 log_output='FILE,TABLE'
添加如上配置重启服务,产生慢查询日志:
慢查询日志文件内容:
执行如下sql语句模拟慢查询:
-- 不会记录到慢查询日志中 select sleep(0.2) ; -- 会记录到慢查询日志中 select sleep(2) ;
① SQL语句中IN包含的值不应过多
MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。再例如:
select id from table_name where numin(1,2,3) 对于连续的数值,能用between 就不要用in了。
② SELECT语句务必指明字段名称
SELECT *增加很多不必要的消耗(cpu、io、内存、网络带宽);增加了使用覆盖索引的可能性;当表结构发生改变时,前断也需要更新。所以要求直接在select后面接上字段名。
③ 如果排序字段没有用到索引,就尽量少排序
④ 如果限制条件中其他字段没有索引,尽量少用or
or两边的字段中,如果有一个不是索引字段,而其他条件也不是索引字段,会造成该查询不走索引的情况。很多时候使用 union all 或者是union(必要的时候)的方式来代替“or”会得到更好的效果
or查询:
(1) or两边放联合索引,不触发索引(如果两边是单列索引另算)
(2) or两边是单列索引,查询走索引
(3) or两边只要有一个不是索引就不启用索引查询
单例索引演示:
复合索引演示:
(4) or两边一个是联合索引的最左索引一个是单例索引才生效,否则失效
示例:
-- 创建单列索引 create index idx_nickname on tb_seller(nickname) ;
使用索引:
![[MySQL面试题-1.png]]
索引失效:
⑤ 不建议使用%前缀模糊查询:例如LIKE “%name”或者LIKE “%name%”,这种查询会导致索引失效而进行全表扫描。但是可以使用LIKE“name%”。
一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是 limit 1000000 , 10,此时需要MySQL排序前1000010 记
录,仅仅返回1000000 - 1000010 的记录,其他记录丢弃,查询排序的代价非常大 。
示例:
explain select * from tb_sku limit 1000000 , 10 ;
执行查询耗时:
优化思路一:在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。
示例:
explain select * from tb_sku s , (select id from tb_sku order by id limit 1000000 , 10 ) t where t.id = s.id ;
执行查询耗时:
优化思路二:该方案适用于主键自增的表,可以把limit 查询转换成某个位置的查询 。
示例:
explain select * from tb_sku where id > 1000000 limit 10 ;
执行查询耗时:
1、设计良好的数据库结构, 允许部分数据冗余, 尽量避免join查询, 提高效率。
2、选择合适的表字段数据类型和存储引擎, 适当的添加索引。
3、MySQL 库主从读写分离。
4、找规律分表, 减少单表中的数据量 ,提高查询速度。
5、添加缓存机制, 比如 memcached, redis等。
6、不经常改动的页面, 生成静态页面。
7、书写高效率的SQL。比如 SELECT * FROM TABEL 改为 SELECT field_1, field_2, field_3 FROM TABLE。
概述:由多个操作组成的一个逻辑单元,组成这个逻辑单元的多个操作要么都成功,要么都失败。
举例:转账
A=Atomicity原子性:就是上面说的,要么全部成功,要么全部失败,不可能只执行一部分操作。
C=Consistency一致性:系统(数据库)总是从一个一致性的状态转移到另一个一致性的状态,不会存在中间状态。
I=Isolation隔离性: 通常来说:一个事务在完全提交之前,对其他事务是不可见的.注意前面的通常来说加了红色,意味着有例外情况。
D=Durability持久性:一旦事务提交,那么就永远是这样子了,哪怕系统崩溃也不会影响到这个事务的结果。
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
丢失修改(Lost to modify):指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
不可重复读(Unrepeatableread):指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
解决方案:对事务进行隔离
MySQL的四种隔离级别如下:
未提交读(READ UNCOMMITTED):这个隔离级别下,其他事务可以看到本事务没有提交的部分修改。因此会造成脏读的问题(读取到了其他事务未提交的部分,而之后该事务进行了回滚)。这个级别的性能没有足够大的优势,但是又有很多的问题,因此很少使用.
sql演示:
# 插入数据 insert into goods_innodb(name) values('华为'); insert into goods_innodb(name) values('小米'); # 会话一 set session transaction isolation level read uncommitted ; # 设置事务的隔离级别为read uncommitted start transaction ; # 开启事务 select * from goods_innodb ; # 查询数据 # 会话二 set session transaction isolation level read uncommitted ; # 设置事务的隔离级别为read uncommitted start transaction ; # 开启事务 update goods_innodb set name = '中兴' where id = 10 ; # 修改数据 # 会话一 select * from goods_innodb ; # 查询数据
已提交读(READ COMMITTED):其他事务只能读取到本事务已经提交的部分。这个隔离级别有不可重复读的问题,在同一个事务内的两次读取,拿到的结果竟然不一样,因为另外一个事务对数据进行了修改。
sql演示:
# 会话一 set session transaction isolation level read committed ; # 设置事务的隔离级别为read committed start transaction ; # 开启事务 select * from goods_innodb ; # 查询数据 # 会话二 set session transaction isolation level read committed ; # 设置事务的隔离级别为read committed start transaction ; # 开启事务 update goods_innodb set name = '中兴' where id = 1 ; # 修改数据 # 会话一 select * from goods_innodb ; # 查询数据 # 会话二 commit; # 提交事务 # 会话一 select * from goods_innodb ; # 查询数据
REPEATABLE READ(可重复读):可重复读隔离级别解决了上面不可重复读的问题(看名字也知道),但是不能完全解决幻读。MySql默认的事务隔离级别就是:
REPEATABLE READ
select @@tx_isolation;
sql演示(解决不可重复读):
# 会话一 start transaction ; # 开启事务 select * from goods_innodb ; # 查询数据 # 会话二 start transaction ; # 开启事务 update goods_innodb set name = '荣耀' where id = 1 ; # 修改数据 # 会话一 select * from goods_innodb ; # 查询数据 # 会话二 commit; # 提交事务 # 会话一 select * from goods_innodb ; # 查询数据
sql演示(测试不会出现幻读的情况):
# 会话一 start transaction ; # 开启事务 select * from goods_innodb ; # 查询数据 # 会话二 start transaction ; # 开启事务 insert into goods_innodb(name) values('小米'); # 插入数据 commit; # 提交事务 # 会话一 select * from goods_innodb ; # 查询数据
sql演示(测试出现幻读的情况):
# 表结构进行修改 ALTER TABLE goods_innodb ADD version int(10) NULL ; # 会话一 start transaction ; # 开启事务 select * from goods_innodb where version = 1; # 查询一条不满足条件的数据 # 会话二 start transaction ; # 开启事务 insert into goods_innodb(name, version) values('vivo', 1); # 插入一条满足条件的数据 commit; # 提交事务 # 会话一 update goods_innodb set name = '金立' where version = 1; # 将version为1的数据更改为'金立' select * from goods_innodb where version = 1; # 查询一条不满足条件的数据
SERIALIZABLE(可串行化):这是最高的隔离级别,可以解决上面提到的所有问题,因为他强制将所以的操作串行执行,这会导致并发性能极速下降,因此也不是很常用。
从对数据操作的粒度分 :
1) 表锁:操作时,会锁定整个表。
2) 行锁:操作时,会锁定当前操作行。
3) 页面锁:会锁定一部分的数据
从对数据操作的类型分:
1) 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
2) 写锁(排它锁):当前操作没有完成之前,它会阻断其他写锁和读锁。
各存储引擎对锁的支持情况:
存储引擎 | 表级锁 | 行级锁 | 页面锁 |
---|---|---|---|
MyISAM | 支持 | 不支持 | 不支持 |
InnoDB | 支持 | 支持 | 不支持 |
MEMORY | 支持 | 不支持 | 不支持 |
BDB | 支持 | 不支持 | 支持 |
MySQL这2种锁的特性可大致归纳如下 :
锁类型 | 特点 |
---|---|
表级锁 | 偏向MyISAM 存储引擎,开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。 |
行级锁 | 偏向InnoDB 存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 |
页面锁 | 开销和加锁时间界于表锁和行锁之间; 会出现死锁; 锁定粒度界于表锁和行锁之间, 并发度一般。 |
从锁的角度来说:表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web 应用;而行级锁则更适合于有大量按索引条件并发更新少量
不同数据,同时又有并查询的应用,如一些在线事务处理(OLTP)系统。
MyISAM 在执行查询语句(SELECT)前,会自动给涉及的表加读锁,在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加
写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁。
显示加表锁语法:
加读锁 : lock table table_name read; 加写锁 : lock table table_name write; 解锁: unlock tables;