Myisam表级锁定,与行级锁定相反,表级别的锁定是MYSQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统处理成本最小,所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好地避免困扰我们的死锁问题。当然,锁定颗粒度大带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并发度大打折扣。
对于MYISAM存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或页级锁定所带来的附加成本要小,锁定本身所消耗的资源也是最少的。但是由于锁定的颗粒度比较大,所以造成锁定资源的争用情况也会比其它的锁定级别都要多,从而在较大程度上会降低并发处理能力。
所以,在优化MYISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。由于锁定级别是不可能改变的了,所以首先须要尽可能地让锁定的时间变短,然后就是让可能并发进行的操作尽可能地并发。
1、缩短锁定时间
缩短锁定时间,说起来容易,做起来难。缩短锁定时间,唯一的办法就是让QUERY执行时间尽可能地短。
(1)尽量避免大的复杂QUERY,将复杂QUERY分拆成几个小的QUERY分步进行;
(2)尽可能地建立足够高效的索引,让数据检索更迅速;
(3)尽量让MYISAM存储引擎的表只存放必要的信息,控制字段类型;
(4)利用合适的机会优化MYISAM表数据文件;
以上四点主要是从IO与CPU方面来考虑提升性能的。
2、分离并行的操作
说到MYISAM的表锁,而且是读写互相阻塞的表锁,可能有些人会认为MYISAM的存储引擎的表上就只能是完全的串行化,没办法再并行了。大家不要忘记了,MYISAM的存储引擎还有一个非常有用的特性,那就是Concurrent Insert(并发插入)的特性。
MYISAM的存储引擎有一个控制是否打开Concurrent Insert或能的参数选项:Concurrent _Insert,可以设置为0、1、2。三个值的具体说明如下:
(1)Concurrent _Insert=2,无论MYISAM存储引擎的表数据文件的中间部分是否存在因为删除数据而留下的空闲空间,都允许在数据文件尾部进行Concurrent _Insert;
(2)Concurrent _Insert=1,当MYISAM存储引擎表数据文件中间不存在空闲空间的时候,可以从文件尾部进行Concurrent _Insert;
(3)Concurrent _Insert=0,无论MYISAM存储引擎的表数据文件中间部分是否存在因为删除数据而留下的空闲空间,都不允许Concurrent _Insert。
所以,如果系统主要以写为主,尤其是有大量INSERT的时候,为了尽可能地提高INSERT的效率,可以将Concurrent_Insert的值设置为2,也就是告诉MYISAM,不管在表中是否有删除行留下的空余空间,都在尾部进行并发插入,使INSERT和SELECT互不干扰;还有就是要控制写入操作的大小,尽量让每次写入操作都能够很快地完成,以防止时间过长地阻塞动作。
3、合理利用读写优先级
MYSQL的表级锁定在默认情况下是写优先级大于读。所以,如果系统是一个以读为主,而且要优先保证查询性能的话,可以通过设置系统参数选项low_priority_updates=1,将写的优先级设置为比读低,即告诉MYSQL尽量先处理读请求;如果系统须要有限保证数据写入的性能的话,则不用设置low_priority_updates参数了。
并发优化的另外一个方面还可以通过开启Key Cache,用来缓存索引来提高读取速度,如果这样还不觉得快的话,还可以通过Query Cache功能来直接缓存Query的结果集。当然,还可以合理利用第三方案Cache软件,如Memcached,来缓存数据,提升系统性能。
4、读写分离
将原数据表进行复制作为slave端,这些数据只用于用户的select操作,因为这些数据是没有经过updatet和delete过的,所以它查询起来非常快,而且也不会出现写锁资源争用的情况;而原数据用于除select以外的其它操作。这样经过读写分离后的数据操作起来速度会更快。当然在建立数据表的同时你可以对表进行适当的分区,经过分区后的数据表,所有数据会分成几块放在不同的区域中,这就好像分成几个硬盘一样,查询的时候首先会将要查的目标数据定位在某个区中进行查询,从而节省了IO与数据检索量,扫描起来就会更快,从而提高查询速度!
为了给高并发情况下的mysql进行更好的优化,有必要了解一下mysql查询更新时的锁表机制。
一、概述
MySQL有三种锁的级别:页级、表级、行级。
MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
MySQL这3种锁的特性可大致归纳如下:
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
二、MyISAM表锁
MyISAM存储引擎只支持表锁,是现在用得最多的存储引擎。
1、查询表级锁争用情况
可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺:
mysql> show status like ‘table%’;
+———————–+———-+
| Variable_name | Value |
+———————–+———-+
| Table_locks_immediate | 76939364 |
| Table_locks_waited | 305089 |
+———————–+———-+
2 rows in set (0.00 sec)
Table_locks_waited的值比较高,说明存在着较严重的表级锁争用情况。
2、MySQL表级锁的锁模式
MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁。
所以对MyISAM表进行操作,会有以下情况:
a、对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。
b、对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。
下面通过例子来进行验证以上观点。数据表gz_phone里有二百多万数据,字段id,phone,ua,day。现在同时用多个客户端同时对该表进行操作分析。
a、当我用客户端1进行一个比较长时间的读操作时,分别用客户端2进行读和写操作:
client1:
mysql>select count(*) from gz_phone group by ua;
75508 rows in set (3 min 15.87 sec)
client2:
select id,phone from gz_phone limit 1000,10;
+——+——-+
| id | phone |
+——+——-+
| 1001 | 2222 |
| 1002 | 2222 |
| 1003 | 2222 |
| 1004 | 2222 |
| 1005 | 2222 |
| 1006 | 2222 |
| 1007 | 2222 |
| 1008 | 2222 |
| 1009 | 2222 |
| 1010 | 2222 |
+——+——-+
10 rows in set (0.01 sec)mysql> update gz_phone set phone=’11111111111′ where id=1001;
Query OK, 0 rows affected (2 min 57.88 sec)
Rows matched: 1 Changed: 0 Warnings: 0
说明当数据表有一个读锁时,其它进程的查询操作可以马上执行,但更新操作需等待读锁释放后才会执行。
b、当用客户端1进行一个较长时间的更新操作时,用客户端2,3分别进行读写操作:
client1:
mysql> update gz_phone set phone=’11111111111′;
Query OK, 1671823 rows affected (3 min 4.03 sec)
Rows matched: 2212070 Changed: 1671823 Warnings: 0
client2:
mysql> select id,phone,ua,day from gz_phone limit 10;
+—-+——-+——————-+————+
| id | phone | ua | day |
+—-+——-+——————-+————+
| 1 | 2222 | SonyEricssonK310c | 2007-12-19 |
| 2 | 2222 | SonyEricssonK750c | 2007-12-19 |
| 3 | 2222 | MAUI WAP Browser | 2007-12-19 |
| 4 | 2222 | Nokia3108 | 2007-12-19 |
| 5 | 2222 | LENOVO-I750 | 2007-12-19 |
| 6 | 2222 | BIRD_D636 | 2007-12-19 |
| 7 | 2222 | SonyEricssonS500c | 2007-12-19 |
| 8 | 2222 | SAMSUNG-SGH-E258 | 2007-12-19 |
| 9 | 2222 | NokiaN73-1 | 2007-12-19 |
| 10 | 2222 | Nokia2610 | 2007-12-19 |
+—-+——-+——————-+————+
10 rows in set (2 min 58.56 sec)
client3:
mysql> update gz_phone set phone=’55555′ where id=1;
Query OK, 1 row affected (3 min 50.16 sec)
Rows matched: 1 Changed: 1 Warnings: 0
说明当数据表有一个写锁时,其它进程的读写操作都需等待读锁释放后才会执行。
3、并发插入
原则上数据表有一个读锁时,其它进程无法对此表进行更新操作,但在一定条件下,MyISAM表也支持查询和插入操作的并发进行。
MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。
a、当concurrent_insert设置为0时,不允许并发插入。
b、当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。
c、当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。
4、MyISAM的锁调度
由于MySQL认为写请求一般比读请求要重要,所以如果有读写请求同时进行的话,MYSQL将会优先执行写操作。这样MyISAM表在进行大量的更新操作时(特别是更新的字段中存在索引的情况下),会造成查询操作很难获得读锁,从而导致查询阻塞。
我们可以通过一些设置来调节MyISAM的调度行为:
a、通过指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。
b、通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
c、通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。
上面3种方法都是要么更新优先,要么查询优先的方法。这里要说明的就是,不要盲目的给mysql设置为读优先,因为一些需要长时间运行的查询操作,也会使写进程“饿死”。只有根据你的实际情况,来决定设置哪种操作优先。这些方法还是没有从根本上同时解决查询和更新的问题。
在一个有大数据量高并发表的mysql里,我们还可采用另一种策略来进行优化,那就是通过mysql主从(读写)分离来实现负载均衡,这样可避免优先哪一种操作从而可能导致另一种操作的堵塞。下面将用一个篇幅来说明mysql的读写分离技术。