【Mysql】太可怕了,跟踪及解决Mysql死锁原来可以这么简单

文章目录

  • 1.存储引擎
  • 2.锁粒度
  • 3.不同粒度锁的比较
  • 4.行锁
  • 5.表锁
  • 6.事务
    • 事务的ACID
    • 事务的隔离级别
    • 事务并发存在问题
    • 使用四种隔离级别来解决事务并发
    • 设置Mysql数据的隔离级别
    • 事务提交的2种方式
    • 修改事务的默认提交方式
    • 事务操作步骤
  • 7.死锁的处理方案
    • 查看死锁
    • 解除死锁

1.存储引擎

主要区别

  1. InnoDB 支持事务,MyISAM 不支持事务。
  2. InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;
  3. InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁,DML(增删改)操作会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。

如何选择

  1. 如果要支持事务请选择 InnoDB,如果不需要可以考虑 MyISAM
  2. 如果表中绝大多数都只是查询,可以考虑 MyISAM,如果既有读写也挺频繁,请使用InnoDB
  3. 系统奔溃后,MyISAM恢复起来更困难,能否接受,不能接受就选 InnoDB;
  4. 如果不知道用什么存储引擎,那就用InnoDB, MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM)

2.锁粒度

MySQL 不同的存储引擎支持不同的锁机制,且以自己的方式显现了锁机制

  • InnoDB 存储引擎既支持行级锁,也支持表级锁,但默认采用行级锁。
    #InnoDB默认参数:innodb_lock_wait_timeout设置等待锁的时间是50s,一旦数据库锁超过这个时间就会报错。
    SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait_timeout';
    SET GLOBAL innodb_lock_wait_timeout=500;
    
  • MyISAM 和 MEMORY 存储引擎采用的是表级锁
  • BDB 存储引擎采用的是页面锁 ,但也支持表级锁

3.不同粒度锁的比较

表级锁

  • 开销小,加锁快;不会出现死锁锁定粒度大发生锁冲突的概率最高,并发度最低。

  • 存储引擎通过总是一次性同时获取所有需要的锁以及总是按相同的顺序获取表锁来避免死锁。

  • 表级锁更适合于以查询为主,并发用户少只有少量按索引条件更新数据的应用

行级锁

  • 开销大,加锁慢;会出现死锁;锁定粒度最小发生锁冲突的概率最低,并发度也最高。

  • 最大程度的支持并发,同时也带来了最大的锁开销

  • 在 InnoDB 中,除单个 SQL 组成的事务外,锁是逐步获得的,这就决定了在 InnoDB 中发生死锁是可能的。

  • 行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用

页面锁

  • 开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

4.行锁

  1. 共享锁(S): 其他 SESSION 可以查询该行数据,且可以对该行数据加 SHARE MODE 的共享锁。但是如果当前事务需要对该数据进行更新操作,则很有可能造成死锁

    SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE;
    
  2. 排他锁(X):其他 SESSION 可以查询该行数据,但是不能对该行数据加共享锁或排他锁,而是等待获得锁

    SELECT * FROM table_name WHERE ... FOR UPDATE
    

5.表锁

  1. 加锁:可以锁定用于当前线程的表。如果表被其他线程锁定,则当前线程会等待,直到可以获取所有锁定为止。

    LOCK TABLE table_name [READ|WRITE]; (READ为读锁、WRITE为写锁)
    
  2. 解锁: 可以释放当前线程获得的任何锁定。当前线程执行另一个 LOCK TABLES 时,或当与服务器的连接被关闭时,所有由当前线程锁定的表被隐式地解锁

    UNLOCK TABLES;
    
  3. LOCK TABLES用法

    • 在用 LOCK TABLES 对 INNODB 表加锁时要注意,要将 AUTOCOMMIT 设为 0,否则MySQL 不会给表加锁;
    • 事务结束前,不要用 UNLOCK TABLES 释放表锁,因为UNLOCK TABLES会隐含地提交事务
    • COMMIT 或 ROLLBACK 并不能释放用 LOCK TABLES 加的表级锁必须用UNLOCK TABLES 释放表锁
  4. 正确的使用方法

    ##如需要写表 t1 并从表 t 读,可以按如下做:
    #1.关闭自动提交
    SET AUTOCOMMIT=0; 
    #2.t1设置为表写锁,t2设置为表读锁
    LOCK TABLES t1 WRITE, t2 READ, ...; 
    #3.执行对t1、t2的sql操作
    #4.提交事务
    COMMIT;  
    #5.解除表锁
    UNLOCK TABLES;
    

6.事务

事务的ACID

  • 原子性(Atomic):事务中任何一个语句执行失败所有已经执行成功的语句也要回滚,整个数据库状态要恢复到执行事务前的状态。 实现主要基于undo log

    MySQL的innoDB存储引擎的undo log(回滚日志是事务原子性和隔离性实现的基础。可以在事务回滚时能够撤销所有已经成功执行的sql语句

    • I 当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
    • undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生rollback时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。
    • 以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态。
  • 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。串行化。InnoDB默认的隔离级别是可重复读,主要基于锁机制、数据的隐藏列、undo log和类next-key lock机制

  • 持久性(Durability):事务完成后对数据库的所有更新将被保存到数据库,不能回滚。实现主要基于redo log

  • 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。其实一致性也是因为原子型的一种表现

    可以说 ,一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障

    • 数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)

    实现一致性的措施包括:

    • 保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证
    • 数据库本身提供保障,如不允许向整形列插入字符串值、字符串长度不能超过列的限制等
    • 应用层面进行保障,如转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致

事务的隔离级别

  • 指当多个事务同时运行时,各事务之间相互隔离,不可互相干扰。如果事务隔离,操作同一批数据时就容易出现脏读、不可重复读和幻读等情况类似于线程隔离

  • 以Java为例更好理解

    • 开启事务:相当于java中开启一个线程,会将共享数据从主内存中拷贝一份副本到当前线程的工作内存中
    • 提交事务将事务内更新的数据从工作内存同步到主内存中
    • 事务隔离:相当于线程隔离,即线程与线程不可见,只能操作自己工作内存,或者通过将工作内存同步到主内存实现线程通信

事务并发存在问题

  • 丢失更新两个事务同时更新一行数据后提交(或撤销)的事务将之前事务提交的数据覆盖了

  • 脏读: 一个事务读取到另一个事务未提交的数据。

  • 不可重复读: 一个事务对同一行数据重复读取两次,但得到的结果不同。

    例如事务A多次查询数据表某条数据却返回了不同的结果,这是因为在查询过程中,数据被B、C、D事务修改并提交了。
    .
    不可重复读和脏读的区别是:

    • 脏读: 读取到其它事务 未提交 的数据
    • 不可重复读: 读取到其他事务 更新并提交 的数据。
  • 虚读/幻读: 一个事务执行两次查询,但第二次查询的结果包含了第一次查询中未出现的数据。

    例如事务A对表中所有数据的某个字段做了从“1”update为“2”的操作,然后事务B在该表中插入一行数据并提交,且这行数据的字段值还是为“1”。 然后事务A再次查询数据发现更新后还有一行的字段值是 “1”其实这行是从事务B中插入的,就好像产生幻觉一样,这就是发生了幻读。
    幻读和不可重复读区别:相同的是都读取了其他事务已提交的数据,不同的是不可重复读查询的都是同一行数据,而幻读针对的是一批数据
    解决办法:解决不可重复读的方法是 行锁(针对单行数据加锁),解决幻读的方式是 表锁(针对整个表加锁)

具体说明:

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么事务A读取到的数据是脏数据,"与表中最终的实际数据不一致"

2、不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。"读取结果与上次结果不一致"

3、幻读:事务A将某学生成绩表的成绩从具体分数修改为ABCDE等级,事务B此时插入一条具体分数的记录,当事务A修改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。"修改过来了但又被改了,导致结果和预期不一样"

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于"修改",幻读侧重于"新增或删除"。解决不可重复读的问题"只需锁住满足条件的行(行锁)",解决幻读需要"锁表(表锁)"

使用四种隔离级别来解决事务并发

  • 读未提交(Read uncommitted) : 产生问题: 脏读、不可重复读、幻读

    • 可以读到其他事务未提交(插入或修改)的数据可避免丢失更新。这是并发最高,一致性最差的隔离级别。
    • 不同事务操作同一行数据互斥
  • 读已提交(Read committed) : (oracle、与Sql server的默认) 产生问题:不可重复读、幻读

    • 可以读到其他事务已提交的数据可避免丢失更新 + 脏读
    • 不同事务操作同一行数据互斥
    • 在大数据量,高并发量的场景下,几乎 不会使用 上述2种隔离级别
  • 可重复读(Repeatable read) : (MysQL默认) 产生的问题:幻读

    事务从开启到提交读到的数据是一致可避免丢失更新 + 脏读 + 不可重复读

    • 不同事务操作同一行数据互斥
  • 串行化(Serializable ) : 可以解决所有的问题

    事务间写->读、写->写串行执行,会导致大量的超时现象和锁竞争,在实际应用中很少使用。

设置Mysql数据的隔离级别

#查看当前系统隔离级别:
SELECT @@global.tx_isolation;

#设置当前系统的隔离级别,隔离级别由低到高设置依次为:
#设置读未提交级别:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
#设置读已提交级别:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
#设置可重复读级别(默认):
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
#设置序列化级别:
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;

事务提交的2种方式

  • 自动提交: mysql默认自动提交,一条DML(增删改)语句会自动commit一次事务。
  • 手动提交: Oracle数据库默认是手动提交事 务需要先start事务,再commit事务

修改事务的默认提交方式

  • 查看事务的默认提交方式: SELECT @@autocommit; --1代表自动提交 0代表手动提交
  • 修改默认提交方式: set @@autocommit = 0;

事务操作步骤

-- 1.开启事务
START TRANSACTION;

-- 2.执行具体sql操作(增删改语句)-----------

-- 3.1.执行DML(增删改)操作正常----commit提交事务
COMMIT;

-- 3.2.执行sql操作异常----rollback回滚事务;
ROLLBACK;

7.死锁的处理方案

查看死锁

#在mysql5.5中,INFORMATION_SCHEMA库中增加了三个关于锁的表(innoDB引擎)
	#当前运行的所有事务: INNODB_TRX        
	#当前出现的锁:INNODB_LOCKS
	#锁等待的对应关系:INNODB_LOCK_WAITS


#1:查看正在进行中的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
 
#2:查看当前锁定的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
 
#3:查看当前等锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

#4. 查询是否锁表
SHOW OPEN TABLES WHERE In_use > 0;

#5. 查看最近死锁的日志
SHOW ENGINE INNODB STATUS;

解除死锁

#命令1.查询进程(如果有SUPER权限,可以看到所有线程。否则,只能看到您自己的线程,取id列做为killId)
SHOW PROCESSLIST
#命令2.查看下在锁的事务 (取trx_mysql_thread_id列做为killId)
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

#1.杀死进程id(就是上面命令1的id列 或 命令2的的trx_mysql_thread_id列)
KILL id;
#2.验证kill后是否还有锁
SHOW OPEN TABLES WHERE In_use > 0;

你可能感兴趣的:(Mysql,mysql,死锁,事务,隔离级别,存储引擎)