MySQL实战(五)mysql锁分析

一、MySQL中的锁
1、在MySQL中,主要定义了以下几种锁:

    页级:引擎 BDB。

    表级:引擎 MyISAM , 理解为锁住整个表,可以同时读,写不行

    行级:引擎 INNODB , 单独的一行记录加锁。

    表级锁粒度大,直接锁定整张表,在锁定期间,其它进程无法对该表进行写操作;如果是读锁,则其它进程读也不允许。

    行级锁粒度小,仅对指定的记录进行加锁,其它进程还是可以对同一个表中的其它记录进行操作。

    页级锁介于表级锁和行级锁之间,一次锁定相邻的一组记录。

2、MySQL具体的锁实现

    MySQL表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock),所以对MyISAM表进行读操作时,它不会阻塞其他用户对同一表的读请求,但会阻塞 对同一表的写操作;而对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作。

    InnoDB有两种模式的行锁:共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁; 排他锁(X):允许获得排他锁的事务。

    为了允许表级锁和行级锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

    1) 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
    2)意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

关系如下表

 

共享锁(S)

排他锁(X)

意向共享锁(IS)

意向排他锁(IX)

共享锁(S)

兼容

冲突

兼容

冲突

排他锁(X)

冲突

冲突

冲突

冲突

意向共享锁(IS)

兼容

冲突

兼容

兼容

意向排他锁(IX)

冲突

冲突

兼容

兼容







意:意向锁是InnoDB自动加的,不需要用户干预;对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X)。
3、MySQL锁定的规则

写锁:如果在表上没有锁,在它上面放一个写锁,否则,把锁定请求放在写锁定队列中。

读锁:如果在表上没有写锁定,把一个读锁定放在它上面;否则,把锁请求放在读锁定队列中。

MySQL对于InnodB和BDB这两种存储引擎,都可能存在死锁,是因为在SQL语句处理期间,InnoDB自动获得行锁定和BDB获得页锁定,而不是在事务启动时获得。三种锁的不同特点:

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

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

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

仅从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用;行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用。

4、MyISAM与InnoDB的实现

 MyISAM锁调度是如何实现的呢?研究表明,写进程将先获得锁(即使读请求先到锁等待队列),这可能导致当有大量的写操作、读操作可能被阻塞。可以指定参数low-priority-updates,使MyISAM默认引擎给予读请求以优先的权利,设置其值为1使优先级降低。

MyISAM表的读操作与写操作之间,以及写操作之间是串行的!当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作,其他线程的读、写操作都会等待,直到锁被释放为止。

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。

在一定条件下,MyISAM表也支持查询和插入操作的并发进行。MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。当设置为0时,不允许并发插入;当设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置;当设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。

InnoDB行锁是通过给索引项加锁实现的,如果没有索引,InnoDB会通过隐藏的聚簇索引来对记录加锁,也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。 行锁分为三种情形:

Record lock :对索引项加锁,即锁定一条记录。

Gap lock:对索引项之间的‘间隙’、对第一条记录前的间隙或最后一条记录后的间隙加锁,即锁定一个范围的记录,不包含记录本身

Next-key Lock:锁定一个范围的记录并包含记录本身(上面两者的结合)。

二、InnoDB产生的死锁问题

MyIsam是不会产生死锁的,因为会总是一次性获得所需的全部锁。在InnoDB中,锁是逐步获得的,就造成了死锁的可能。发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。
1、查看表的锁定情况,可以通过命令:
#表锁定争夺
SHOW STATUS LIKE 'table%';
#行锁定争夺
SHOW STATUS LIKE 'innodb_row%';

如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,还可以通过设置InnoDB Monitors来进一步观察发生锁冲突的表、数据行等,并分析锁争用的原因。

2、MySQL是通过wait-for graph算法进行死锁检查的。

每当加锁请求无法满足并进入等待时,就会自动触发算法。所以innoDB讲事务看成一个个节点,将资源看成是锁。

三、减少死锁的策略

1)在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。

2)在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。

3)在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT...FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可避免问题。



你可能感兴趣的:(MySQL进阶)