【MySQL数据库】MySQL存储引擎

MySQL存储引擎

在MySQL数据库中,存储引擎的选择对于数据库的性能、事务支持、数据完整性等方面有着至关重要的影响。下面将详细讲解MySQL中的存储引擎,特别是InnoDB和MyISAM,以及InnoDB行锁与索引的关系。

MySQL存储引擎

MySQL支持多种存储引擎,每种存储引擎都有其特定的使用场景和优缺点。其中,InnoDB和MyISAM是最常用的两种存储引擎。

MyISAM

  • 特点
    • 不支持事务和外键约束。
    • 占用资源较小,访问速度快。
    • 表级锁定,这意味着在写操作时,整个表会被锁定,其他读或写操作必须等待。
    • 支持全文索引,适用于需要全文搜索的应用场景。
  • 存储格式
    • 静态表:字段都是固定长度的,存储迅速,容易缓存,但占用空间多。
    • 动态表:包含可变字段,占用空间少,但更新、删除会产生碎片,需要定期优化。
    • 压缩表:通过myisamchk工具创建,占用空间极小,但访问速度稍慢。

InnoDB

  • 特点
    • 支持事务处理和外键约束,适用于需要数据一致性和完整性的应用场景。
    • 缓存能力较好,支持行级锁定,读写并发能力较强。
    • 从MySQL 5.5版本开始支持全文索引。

查看和修改存储引擎

查看存储引擎

  • 查看系统支持的存储引擎:
    SHOW ENGINES;
    
  • 查看表使用的存储引擎:
    SHOW TABLE STATUS FROM 库名 WHERE NAME='表名'\G;
    
    或者:
    USE 库名;
    SHOW CREATE TABLE 表名;
    

修改存储引擎

  • 通过ALTER TABLE修改:
    USE 库名;
    ALTER TABLE 表名 ENGINE=MyISAM;
    
  • 通过修改配置文件指定默认存储引擎:
    /etc/my.cnf中添加或修改以下配置,并重启MySQL服务:
    [mysqld]
    default-storage-engine=INNODB
    
    注意:此方法只对修改配置文件并重启MySQL服务后新创建的表有效。
  • 在创建表时指定存储引擎:
    USE 库名;
    CREATE TABLE 表名(字段1 数据类型,...) ENGINE=MyISAM;
    

InnoDB行锁与索引的关系

InnoDB的行锁是通过给索引项加锁来实现的。不同的查询条件会导致不同的锁行为。

  1. 主键索引

    DELETE FROM t1 WHERE id=1;
    

    如果id字段是主键,InnoDB会直接锁住整行记录。

  2. 普通索引

    DELETE FROM t1 WHERE name='aaa';
    

    如果name字段是普通索引,InnoDB会锁住索引对应的两行记录(即满足条件的记录的前后两行,形成一个范围锁)。

  3. 无索引

    DELETE FROM t1 WHERE age=23;
    

    如果age字段没有索引,InnoDB会使用全表扫描来过滤记录。这时,表上的各行记录都可能被加上锁,导致性能下降。
    因此,在使用InnoDB时,为了提高并发性能和避免不必要的锁等待,建议尽量为查询条件中的字段建立索引。

死锁

死锁是指两个或多个事务在执行过程中,因争夺资源(如锁)而造成的一种互相等待的现象。这种等待若无外力介入,将无限持续下去,导致事务无法继续运行,系统处于死锁状态。
例如,如果事务A锁住了记录1并等待记录2,而事务B锁住了记录2并等待记录1,这样两个事务就发生了死锁现象。计算机系统中,如果系统的资源分配策略不当,更常见的可能是程序员写的程序有错误等,则会导致进程因竞争资源不当而产生死锁的现象。

死锁危害

众所周知,数据库的连接资源是很珍贵的,如果一个连接因为事务阻塞长时间不释放,那么后面新的请求要执行的sql也会排队等待,越积越多,最终会拖垮整个应用。一旦你的应用部署在微服务体系中而又没有做熔断处理(当某服务出现不可用或响应超时的情况时,会暂时停止对该服务的调用),由于整个链路被阻断,那么就会引发雪崩效应,导致很严重的生产事故。

  1. 资源占用:死锁会导致数据库连接、锁资源等长时间被占用,无法释放。
  2. 性能下降:新请求因资源被占用而排队等待,导致系统响应时间变长,性能下降。
  3. 系统崩溃:在微服务体系中,若未做熔断处理,死锁可能引发雪崩效应,导致整个应用崩溃。

死锁案例

场景描述

在这个案例中,我们有两个数据库会话(session 1 和 session 2),它们各自开始了一个事务,并尝试锁定不同的行,然后尝试锁定对方已经锁定的行。

表结构和数据
create table t1(id int primary key, name char(3), age int);
insert into t1 values(1,'aaa',22);
insert into t1 values(2,'bbb',23);
insert into t1 values(3,'aaa',24);
insert into t1 values(4,'bbb',25);
insert into t1 values(5,'ccc',26);
insert into t1 values(6,'zzz',27);
会话操作

Session 1:

begin;
select * from t1 where id=1 for update;  -- 锁定了id=1的行
-- 此时session 1等待session 2释放id=2的锁
select * from t1 where id=2 for update;  -- 等待,因为id=2的行被session 2锁定了

Session 2:

begin;
select * from t1 where id=2 for update;  -- 锁定了id=2的行
-- 此时session 2等待session 1释放id=1的锁
select * from t1 where id=1 for update;  -- 死锁发生,因为id=1的行被session 1锁定了
分析
  1. 锁的类型
    • for update 语句会为选中的行加上排他锁(X锁)。这意味着在事务完成之前,其他事务无法修改或删除这些行,也无法对这些行加上排他锁或共享锁(在大多数数据库隔离级别下)。
  • 共享锁(S锁):允许事务读取一行数据,但不允许修改。多个事务可以同时持有对同一行的共享锁。
  • 排他锁(X锁):允许事务读取和修改一行数据。在事务持有排他锁期间,其他事务无法对该行加任何锁。
  1. 死锁的发生

    • Session 1 锁定了 id=1 的行,并尝试锁定 id=2 的行(但此时 id=2 被 Session 2 锁定了)。
    • Session 2 锁定了 id=2 的行,并尝试锁定 id=1 的行(但此时 id=1 被 Session 1 锁定了)。
    • 这导致了一个循环等待条件:Session 1 等待 Session 2 释放 id=2 的锁,而 Session 2 等待 Session 1 释放 id=1 的锁。
  2. 死锁的检测和解决

    • 大多数现代数据库管理系统(如MySQL的InnoDB存储引擎)都具备死锁检测机制。
    • 当检测到死锁时,数据库会选择一个事务进行回滚,以打破循环等待条件。
    • 在这个案例中,数据库可能会选择回滚 Session 1 或 Session 2 中的一个事务,以释放锁并允许另一个事务继续执行。

避免死锁的策略

  1. 设置锁等待超时

    • 通过设置innodb_lock_wait_timeout参数,可以指定事务在等待锁资源时的超时时间。一旦超时,事务将回滚并释放锁资源。
    • innodb_rollback_on_timeout参数控制是否在等待超时时回滚事务(默认不回滚)。
  2. 开启死锁检测

    • InnoDB存储引擎支持死锁检测。当检测到死锁时,InnoDB会自动选择一个事务进行回滚,以打破死锁。
    • 可以通过innodb_deadlock_detect参数查看和设置死锁检测是否开启。
  3. 优化业务逻辑

    • 尽量按照相同的顺序访问数据库表和记录,以减少死锁的可能性。
    • 避免同时锁定多个资源,尽量将锁定操作限制在最小范围内。
  4. 保持事务简短

    • 尽量减少事务的持续时间,以减少对资源的占用时间和范围。
    • 避免长事务,以减少完成事务可能的延迟和锁资源的占用。
  5. 添加合理索引

    • 为表添加合适的索引,以减少全表扫描的次数和时间。
    • 索引可以加快查询速度,减少锁资源的占用时间。
  6. 降低隔离级别

    • 如果业务允许,可以降低数据库的隔离级别。例如,将隔离级别从可重复读(RR)调整为读已提交(RC),以减少间隙锁的使用和死锁的发生。
  7. 使用乐观锁

    • 在读多写少的场景下,可以使用乐观锁机制。乐观锁不会在上锁时阻塞其他事务的读取操作,而是在更新时检查数据是否被其他事务修改过。
    • 如果数据被修改过,则放弃更新操作;否则,执行更新操作。
乐观锁与悲观锁
  • 乐观锁:基于数据版本记录机制实现。在更新数据时,会检查数据版本是否发生变化。如果版本未变,则执行更新操作;如果版本已变,则放弃更新。适用于读多写少的场景。
  • 悲观锁:在读取数据时直接加锁,以防止其他事务修改数据。加锁期间,其他事务无法读取或修改被锁定的数据。适用于写多或需要保证数据一致性的场景。

总结

存储引擎定义

存储引擎时MySQL数据库的一个核心组件,负责执行实际的数据IO操作(数据的存储和提取)。
工作在文件系统之上,数据库的存储数据会先将数据传输到存储引擎,再按照存储引擎的存储格式保存到文件系统。

InnoDB和MyISAM存储引擎的特点

存储引擎 事务支持 外键约束 锁定级别 读写并发能力 全文索引支持 文件存储 适用场景
MyISAM 不支持 不支持 表级锁定 较低(读写会相互阻塞) 支持 .frm(表结构文件), .MYD(表数据文件), .MYI(索引文件) 不需要事务处理,单独写入或查询的应用场景
InnoDB 支持 支持 行级锁定(全表扫描时为表级锁定) 较好 5.5版本后开始支持 .frm(表结构文件), .ibd(表空间文件) 需要事务支持,一致性要求高,数据更新频繁的应用场景

存储引擎管理操作

操作类型 SQL语句/命令 描述
修改已存在表的存储引擎 ALTER TABLE 表名 ENGINE=InnoDB/MyISAM; 针对已经存在的表,修改其存储引擎为InnoDB或MyISAM
新建表时指定存储引擎 CREATE TABLE 表名 (...) ENGINE=InnoDB/MyISAM; 在新建表时,指定表的存储引擎为InnoDB或MyISAM
设置默认存储引擎(会话级) SET SESSION default_storage_engine=InnoDB/MyISAM; 为当前会话设置默认的存储引擎为InnoDB或MyISAM
设置默认存储引擎(全局级) SET GLOBAL default_storage_engine=InnoDB/MyISAM; 为全局设置默认的存储引擎为InnoDB或MyISAM,影响之后新建的所有表(直到服务器重启或再次更改)
持久化设置默认存储引擎(配置文件) vim /etc/my.cnf 并添加 default-storage-engine=InnoDB/MyISAM 在MySQL的配置文件中设置默认的存储引擎,重启服务器后生效
查看当前会话的默认存储引擎 SHOW SESSION VARIABLES LIKE 'default_storage_engine'; 显示当前会话的默认存储引擎设置
查看全局的默认存储引擎 SHOW GLOBAL VARIABLES LIKE 'default_storage_engine'; 显示全局的默认存储引擎设置
查看表的存储引擎 SHOW CREATE TABLE 表名; 显示表的创建语句,其中包括存储引擎信息
查看表状态(包含存储引擎信息) SHOW TABLE STATUS WHERE name='表名'\G 以垂直格式显示指定表的状态信息,其中包括存储引擎

问答环节

如何尽可能避免死锁?
1)设置锁等待超时时间:即两个事务相互等待时,一旦等待时间超过了这个时间之后,那么超时事务回滚释放资源,另一个事务就能正常执行了。
在 InnoDB 存储引擎中,参数 innodb_lock_wait_timeout 是用来设置超时时间的,默认值为 50 秒。 show VARIABLES like ‘innodb_lock_wait_timeout’;
参数 innodb_rollback_on_timeout 表示是否在等待超时时对进行中的事务进行回滚操作(默认是OFF,代表不回滚)。
2)主动开启死锁检测:当 innodb 检测发现死锁之后,就会进行回滚死锁的事物。
show VARIABLES like ‘innodb_deadlock_detect’; #查看当前死锁检测是否开启
set global innodb_deadlock_detect = ON; #ON为开启死锁检测,OFF为关闭
3)使用更合理的业务逻辑。对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定多个资源。
4)保持事务简短。减少对资源的占用时间和占用范围,避免长事务,减少完成事务可能的延迟并释放锁。
5)为表添加合理的索引。如果不使用索引将会发生全表扫描,扫描时间长,占用资源多,且耗时,会导致死锁的概率大大增加。
6)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为间隙锁造成的死锁。
7)读多写少的场景下使用乐观锁机制,读取数据不上锁,在读的情况下可以共享资源,这样可以省去了锁的开销,提高了吞吐量。

乐观锁是什么,如何配置?
乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据,如果别人修改了数据则放弃操作,否则执行操作。适用于读多写少的场景。
SELECT * from t1 where id = 1 lock in share MODE;

悲观锁是什么,如何配置?
悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。一般适用于写多的场景。
SELECT * from t1 where id = 1 for update;

猜你喜欢

转载自blog.csdn.net/Karoku/article/details/142959473