0
点赞
收藏
分享

微信扫一扫

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

文章目录

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.事务

事务的隔离级别概念

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

  • 以Java为例更好理解

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

存在问题

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

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

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

  • 虚读/幻读: 一个事务执行两次查询,但第二次查询的结果包含了第一次查询中未出现的数据。

数据库用四种隔离级别来解决上述问题:

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

  • 读已提交(Read committed) : (oracle、与Sql server的默认) 产生问题:不可重复读、幻读

  • 可重复读(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;
举报

相关推荐

0 条评论