页级:引擎 BDB。
表级:引擎 MyISAM , 理解为锁住整个表,可以同时读,写不行
行级:引擎 INNODB , 单独的一行记录加锁。 表级锁粒度大,直接锁定整张表,在锁定期间,其它进程无法对该表进行写操作;如果是读锁,则其它进程读也不允许。
行级锁粒度小,仅对指定的记录进行加锁,其它进程还是可以对同一个表中的其它记录进行操作。
页级锁介于表级锁和行级锁之间,一次锁定相邻的一组记录。
2、MySQL具体的锁实现
MySQL表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock),所以对MyISAM表进行读操作时,它不会阻塞其他用户对同一表的读请求,但会阻塞 对同一表的写操作;而对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作。
InnoDB有两种模式的行锁:共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁; 排他锁(X):允许获得排他锁的事务。
为了允许表级锁和行级锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。
关系如下表
共享锁(S) |
排他锁(X) |
意向共享锁(IS) |
意向排他锁(IX) |
|
共享锁(S) |
兼容 |
冲突 |
兼容 |
冲突 |
排他锁(X) |
冲突 |
冲突 |
冲突 |
冲突 |
意向共享锁(IS) |
兼容 |
冲突 |
兼容 |
兼容 |
意向排他锁(IX) |
冲突 |
冲突 |
兼容 |
兼容 |
写锁:如果在表上没有锁,在它上面放一个写锁,否则,把锁定请求放在写锁定队列中。
读锁:如果在表上没有写锁定,把一个读锁定放在它上面;否则,把锁请求放在读锁定队列中。
MySQL对于InnodB和BDB这两种存储引擎,都可能存在死锁,是因为在SQL语句处理期间,InnoDB自动获得行锁定和BDB获得页锁定,而不是在事务启动时获得。三种锁的不同特点:
1) 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
2) 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
3) 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
仅从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用;行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用。
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表中有没有空洞,都允许在表尾并发插入记录。
Record lock :对索引项加锁,即锁定一条记录。
Gap lock:对索引项之间的‘间隙’、对第一条记录前的间隙或最后一条记录后的间隙加锁,即锁定一个范围的记录,不包含记录本身
Next-key Lock:锁定一个范围的记录并包含记录本身(上面两者的结合)。
#表锁定争夺
SHOW STATUS LIKE 'table%';
#行锁定争夺
SHOW STATUS LIKE 'innodb_row%';
如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,还可以通过设置InnoDB Monitors来进一步观察发生锁冲突的表、数据行等,并分析锁争用的原因。
1)在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。
2)在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。
3)在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT...FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可避免问题。