MySQL中用于
WRITE(写)
的表锁的实现机制如下:
- 如果表没有加锁,那么就加一个写锁。
- 否则的话,将请求放到写锁队列中。
MySQL中用于 READ(读)
的表锁的实现机制如下:
- 如果表没有加写锁,那么就加一个读锁。
- 否则的话,将请求放到读锁队列中。
当锁释放后,写锁队列中的线程可以用这个锁资源,然后才轮到读锁队列中的线程。
这就是说,如果表里有很多更新操作的话,那么 Select
必须等到所有的更新都完成了之后才能开始。
从 MySQL 3.23.33 开始,可以通过状态变量 Table_locks_waited
和 Table_locks_immediate
来分析系统中的锁表争夺情况:
mysql> SHOW STATUS LIKE 'Table%';+-----------------------+---------+| Variable_name | Value |+-----------------------+---------+| Table_locks_immediate | 1151552 || Table_locks_waited | 15324 |+-----------------------+---------+
在 MySQL 3.23.7(在Windows上是3.23.25)以后,在 MyISAM
表中只要没有冲突的 Insert
操作,就可以无需使用锁表自由地并行执行 Insert
和 Select
语句。也就是说,可以在其它客户端正在读取 MyISAM
表记录的同时时插入新记录。如果数据文件的中间没有空余的磁盘块的话,就不会发生冲突了,因为这种情况下所有的新记录都会写在数据文件的末尾(当在表的中间做删除或者更新操作时,就可能导致空洞)。当空洞被新数据填充后,并行插入特性就会自动重新被启用了。
如果想要在一个表上做大量的 Insert
和Select
操作,但是并行的插入却不可能时,可以将记录插入到临时表中,然后定期将临时表中的数据更新到实际的表里。可以用以下命令实现:
mysql> LOCK TABLES real_table WRITE, insert_table WRITE;mysql> Insert INTO real_table Select * FROM insert_table;mysql> TRUNCATE TABLE insert_table;mysql> UNLOCK TABLES;
InnoDB
使用行级锁,BDB
使用页级锁。对于InnoDB
和 BDB
存储引擎来说,是可能产生死锁的。这是因为 InnoDB
会自动捕获行锁,BDB
会在执行 SQL 语句时捕获页锁的,而不是在事务的开始就这么做。
行级锁的优点有:
- 在很多线程请求不同记录时减少冲突锁。
- 事务回滚时减少改变数据。
- 使长时间对单独的一行记录加锁成为可能。
行级锁的缺点有:
- 比页级锁和表级锁消耗更多的内存。
- 当在大量表中使用时,比页级锁和表级锁更慢,因为他需要请求更多的所资源。
- 当需要频繁对大部分数据做
GROUP BY
操作或者需要频繁扫描整个表时,就明显的比其它锁更糟糕。 - 使用更高层的锁的话,就能更方便的支持各种不同的类型应用程序,因为这种锁的开销比行级锁小多了。
表级锁在下列几种情况下比页级锁和行级锁更优越:
- 很多操作都是读表。
- 在严格条件的索引上读取和更新,当更新或者删除可以用单独的索引来读取得到时:
Update tbl_name SET column=value Where unique_key_col=key_value; Delete FROM tbl_name Where unique_key_col=key_value;
Select
和Insert
语句并发的执行,但是只有很少的Update
和Delete
语句。- 很多的扫描表和对全表的
GROUP BY
操作,但是没有任何写表。
表级锁和行级锁或页级锁之间的不同之处还在于:
将同时有一个写和多个读的地方做版本(例如在MySQL中的并发插入)。也就是说,数据库/表支持根据开始访问数据时间点的不同支持各种不同的试图。其它名有:时间行程,写复制,或者是按需复制。
原文: Versioning (such as we use in MySQL for concurrent inserts)where you can have one writer at the same time as many readers.This means that the database/table supports different views for thedata depending on when you started to access it. Other names forthis are time travel, copy on write, or copy on demand.
按需复制在很多情况下比页级锁或行级锁好多了。尽管如此,最坏情况时还是比其它正常锁使用了更多的内存。
可以用应用程序级锁来代替行级锁,例如MySQL中的 GET_LOCK()
和 RELEASE_LOCK()
。但它们是劝告锁(原文:These are advisorylocks),因此只能用于安全可信的应用程序中。
7.3.2 锁表
为了能有快速的锁,MySQL除了 InnoDB
和BDB
这两种存储引擎外,所有的都是用表级锁(而非页、行、列级锁)。
对于 InnoDB
和 BDB
表,MySQL只有在指定用 LOCKTABLES
锁表时才使用表级锁。在这两种表中,建议最好不要使用 LOCK TABLES
,因为 InnoDB
自动采用行级锁,BDB
用页级锁来保证事务的隔离。
如果数据表很大,那么在大多数应用中表级锁会比行级锁好多了,不过这有一些陷阱。
表级锁让很多线程可以同时从数据表中读取数据,但是如果另一个线程想要写数据的话,就必须要先取得排他访问。正在更新数据时,必须要等到更新完成了,其他线程才能访问这个表。
更新操作通常认为比读取更重要,因此它的优先级更高。不过最好要先确认,数据表是否有很高的 Select
操作,而更新操作并非很‘急需’。
表锁锁在一个线程在等待,因为磁盘空间满了,但是却需要有空余的磁盘空间,这个线程才能继续处理时就有问题了。这种情况下,所有要访问这个出问题的表的线程都会被置为等待状态,直到有剩余磁盘空间了。
表锁在以下设想情况中就不利了:
- 一个客户端提交了一个需要长时间运行的
Select
操作。 - 其他客户端对同一个表提交了
Update
操作,这个客户端就要等到Select
完成了才能开始执行。 - 其他客户端也对同一个表提交了
Select
请求。由于Update
的优先级高于Select
,所以Select
就会先等到Update
完成了之后才开始执行,它也在等待第一个Select
操作。
下列所述可以减少表锁带来的资源争夺:
- 让
Select
速度尽量快,这可能需要创建一些摘要表。 - 启动
mysqld
时使用参数--low-priority-updates
。这就会让更新操作的优先级低于Select
。这种情况下,在上面的假设中,第二个Select
就会在Insert
之前执行了,而且也无需等待第一个Select
了。 - 可以执行
SETLOW_PRIORITY_UpdateS=1
命令,指定所有的更新操作都放到一个指定的链接中去完成。详情请看“14.5.3.1SET
Syntax”。 - 用
LOW_PRIORITY
属性来降低Insert
,Update
,Delete
的优先级。 - 用
HIGH_PRIORITY
来提高Select
语句的优先级。详情请看“14.1.7Select
Syntax”。 - 从MySQL 3.23.7 开始,可以在启动
mysqld
时指定系统变量max_write_lock_count
为一个比较低的值,它能强制临时地提高表的插入数达到一个特定值后的所有Select
操作的优先级。它允许在WRITE
锁达到一定数量后有READ
锁。 - 当
Insert
和Select
一起使用出现问题时,可以转而采用MyISAM
表,它支持并发的Select
和Insert
操作。 - 当在同一个表上同时有插入和删除操作时,
InsertDELAYED
可能会很有用。详情请看“14.1.4.2Insert DELAYED
Syntax”。 - 当
Select
和Delete
一起使用出现问题时,Delete
的LIMIT
参数可能会很有用。详情请看“14.1.1Delete
Syntax” - 执行
Select
时使用SQL_BUFFER_RESULT
有助于减短锁表的持续时间.详情请看“14.1.7Select
Syntax”。 - 可以修改源代码 `mysys/thr_lock.c',只用一个所队列。这种情况下,写锁和读锁的优先级就一样了,这对一些应用可能有帮助。
以下是MySQL锁的一些建议:
- 只要对同一个表没有大量的更新和查询操作混在一起,目前的用户并不是问题。
- 执行
LOCK TABLES
来提高速度(很多更新操作放在一个锁之中比没有锁的很多更新快多了)。将数据拆分开到多个表中可能也有帮助。 - 当MySQL碰到由于锁表引起的速度问题时,将表类型转换成
InnoDB
或BDB
可能有助于提高性能。详情请看“16 TheInnoDB
Storage Engine”和“15.4 TheBDB
(BerkeleyDB
) Storage Engine”。