More Related Content Similar to 基于Innodb开发的最佳实践 Similar to 基于Innodb开发的最佳实践 (20) 基于Innodb开发的最佳实践5. 案例一:为什么Insert这么慢
• 背景
向一个表进行insert数据,为啥一秒钟才能写入500条
(有可能更少)
• 表结构:
CREATE TABLE `t1_rnd` (
`id` int(10) unsigned NOT NULL ,
`c1` varchar(64) NOT NULL,
`c2` varchar(255) DEFAULT NULL,
`c3` int(10) unsigned DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
• 写入程序: 备注中
6. 案例一:为什么insert这么慢
$i_end = (int) '999999999‘;
$i_start = 1000000000;
for ($i = $i_start; $i < $i_end; $i++) {
$x = mt_rand();
$sql = "INSERT INTO t1_rnd
VALUES($x, 'name_$x', 'desc_$x', $i)";
$res = $pdo->exec($sql);
...
}
7. 案例一:为什么insert这么慢
记数从:100000 开始 ok 101282 w, per: 1079 /s
ok 100503 w, per: 12307 /s ok 101283 w, per: 1093 /s
ok 100504 w, per: 13369 /s ok 101284 w, per: 836 /s
... ok 101285 w, per: 942 /s
ok 100508 w, per: 14082 /s ok 101286 w, per: 1021 /s
ok 101287 w, per: 1033 /s
ok 100509 w, per: 11058 /s
ok 101288 w, per: 816 /s
ok 100510 w, per: 12853 /s ok 101289 w, per: 802 /s
… ok 101290 w, per: 1040 /s
ok 101201 w, per: 15592 /s ok 101291 w, per: 778 /s
ok 101202 w, per: 11037 /s ok 101292 w, per: 776 /s
ok 101203 w, per: 13454 /s ...
ok 101204 w, per: 12482 /s ok 101360 w, per: 618 /s
ok 101205 w, per: 7162 /s ok 101361 w, per: 611 /s
ok 101362 w, per: 580 /s
ok 101206 w, per: 6323 /s
ok 101363 w, per: 593 /s
ok 101207 w, per: 4655 /s ...
ok 101208 w, per: 6212 /s ok 101372 w, per: 563 /s
ok 101209 w, per: 3960 /s ok 101373 w, per: 554 /s
ok 101210 w, per: 2559 /s ok 101374 w, per: 504 /s
ok 101211 w, per: 2170 /s ok 101375 w, per: 547 /s
... ok 101376 w, per: 569 /s
...
8. 案例一分析
• 查看基本情况
– mysql> select count(*) from t1_rnd;
– +----------+
– | count(*) |
– +----------+
– | 13719413 |
– +----------+
– 1 row in set (12 min 14.59 sec)
• 文件大小:
– -rw-rw---- 1 mysql3306 mysql 8.5K 08-09 15:01 t1_rnd.frm
– -rw-rw---- 1 mysql3306 mysql 1.4G 08-09 15:50 t1_rnd.ibd
9. 案例一分析
• 查看基本情况:
– 通过Percona-Server的慢日值功能可以查看一下count有多少个
page被读到,读了多少行。
– mysql>set global log_slow_verbosity=full;
– mysql>select count(*) from t1_rnd;
查看慢日值
10. 慢日值内容:
# Thread_id: 36 Schema: wubx Last_errno: 0 Killed: 0
# Query_time: 719.255557 Lock_time: 0.000051 Rows_sent: 1 Rows_examined:
13719413 Rows_affected: 0 Rows_read: 18446744073693256119
# Bytes_sent: 70 Tmp_tables: 0 Tmp_disk_tables: 0 Tmp_table_sizes: 0
# InnoDB_trx_id: 4A0B331
# QC_Hit: No Full_scan: Yes Full_join: No Tmp_table: No Tmp_table_on_disk: No
# Filesort: No Filesort_on_disk: No Merge_passes: 0
# InnoDB_IO_r_ops: 74732 InnoDB_IO_r_bytes: 1224409088 InnoDB_IO_r_wait:
713.597767
# InnoDB_rec_lock_wait: 0.000000 InnoDB_queue_wait: 0.000000
# InnoDB_pages_distinct: 65254
SET timestamp=1312882181;
select count(*) from t1_rnd;
11. • 基本分析:
mysql> select 65254*16/1024;
– +---------------+
– | 65254*16/1024 |
– +---------------+
– | 1019.5938 |
– +---------------+
计算一下数据大小:
– mysql> select (4+30+30+4)*13719413/1024/1024;
– +--------------------------------+
– | (4+4+30+30)*13719413/1024/1024 |
– +--------------------------------+
– | 889.70192337 |
– +--------------------------------+
12. 案例一分析
• 思考问题:
– 为什么我们计算需要存储该表的大小和实质物
理大小相差那么大呢?
• 是索引增加了文件大小?
– 为什么count那么慢,Rows_read:
18446744073693256119 !!!!
14. 案例一分析
• Count统计太慢:
• Innodb本身没这方计数器,如果进行无条件
的count实质上相当于把表的btree page全部
读取一次。
• 对于本身碎片很严重的表,这种全表扫描会
被成一很严重的随机IO操作
• 那个Read_Rows很大数据,从实质上理解
是错误的,这就是Bug.
15. • 从测试数据来看在写入1200万前,性能还能接
受。
• 是不是可以考虑对于大表进行拆表处理?
• 如果该表存在range类的操作,拆表是不是最佳的
方式呢?
• 对于有碎片明显的表,如何处理能让快点呢?
• 对于无条件count innodb做了什么?
16. 讨论解决方案
看看表碎片比较少的情况的操作:
• 对现存的表碎片整理的操作:
mysql> alter table theme_irnd engine=Innodb;
Query OK, 13719413 rows affected (34 min 48.24 sec)
• 整理后文件大小:
-rw-rw---- 1 mysql3306 mysql 848M 08-09 18:23 theme.ibd
mysql> select count(*) from theme;
+----------+
| count(*) |
+----------+
| 13719413 |
+----------+
1 row in set (4.92 sec)
• 如针对range操作,这样是不是已很好了
17. • 针对range的在加速:
mysql> create index idx_c3 on t1_rnd(c3);
Query OK, 0 rows affected (44.37 sec)
mysql> select count(*) from t1_rnd;
+----------+
| count(*) |
+----------+
| 13719413 |
+----------+
1 row in set (1.84 sec)
• 什么情况,从原来的
12 min 14.59 sec->4.92 sec->1.84 sec?
18. 讨论解决方案
• 对于无条件count innodb做了什么?
添加完idx_c3索引后:
# Thread_id: 44 Schema: wubx Last_errno: 0 Killed: 0
# Query_time: 1.841461 Lock_time: 0.000051 Rows_sent: 1 Rows_examined:
13719413 Rows_affected: 0 Rows_read: 13719413
# Bytes_sent: 70 Tmp_tables: 0 Tmp_disk_tables: 0 Tmp_table_sizes: 0
# InnoDB_trx_id: 4A0B89B
# QC_Hit: No Full_scan: Yes Full_join: No Tmp_table: No Tmp_table_on_disk:
No
# Filesort: No Filesort_on_disk: No Merge_passes: 0
# InnoDB_IO_r_ops: 0 InnoDB_IO_r_bytes: 0 InnoDB_IO_r_wait: 0.000000
# InnoDB_rec_lock_wait: 0.000000 InnoDB_queue_wait: 0.000000
# InnoDB_pages_distinct: 12252
SET timestamp=1312970168;
select count(*) from t1_rnd;
• 为什么用普通索引进行的count比PK快呢?
19. 总结经验及运用
• 总结:
– 在写入数据时保证主建是有顺的从小到大的方
式写入
– 对于Innodb要求快速写入的表添加一列自增的
自建做为索引
– 对于Innodb表的使用要保持一个好的习惯,尽
量基于唯一索引去访问数据(排除range)
– 如果对于Innodb有大量的update操作,同时又
有大量的range操作,请考虑定期进行整理碎
片
20. 总结经验及运用
• 验证:
同样的表结构,在写入程序做以下调整:
for ($i = $i_start; $i < $i_end; $i++) {
$x = $i;
$sql = "INSERT INTO t1_rnd
VALUES($x, 'name_$x', 'desc_$x', $i)";
$res = $pdo->exec($sql);
...
}
21. 总结经验及运用
计数:100000W开始 ...
ok 100498 w, per: 21769 /s ok 102991 w, per: 21515 /s
ok 100499 w, per: 21384 /s
ok 100500 w, per: 21770 /s
ok 102992 w, per: 21772 /s
ok 100501 w, per: 21376 /s ok 102993 w, per: 18120 /s
ok 100502 w, per: 21749 /s ok 102994 w, per: 19615 /s
... ok 102995 w, per: 21163 /s
ok 101380 w, per: 21624 /s ok 102996 w, per: 21782 /s
ok 101381 w, per: 21329 /s ok 102997 w, per: 21377 /s
ok 101382 w, per: 21632 /s
ok 101383 w, per: 21592 /s
ok 102998 w, per: 21705 /s
ok 101384 w, per: 21377 /s ok 102999 w, per: 13881 /s
ok 101385 w, per: 18434 /s ok 103000 w, per: 20969 /s
ok 101386 w, per: 19711 /s ...
...
写入到3000W行数据性能也很好
26. 案例二:死锁
• 背景:
利用show engine innodb statusG;
查看LATEST DETECTED DEADLOCK部分经常有死锁现象
死锁语句:
DELETE FROM GRP_OfflineNotify WHERE
UserId = 852577338 AND GroupId = 33990861 AND
NotifyType = 10
DELETE FROM GRP_OfflineNotify WHERE
UserId = 825749088 AND GroupId = 33990861 AND
NotifyType = 10
27. 案例二 分析
• 了解LATEST DETECTED DEADLOCK :
>show engine innodb statusG;
LATEST DETECTED DEADLOCK
------------------------
这个死锁信息发生的时间
*** (N) TRANSACTION:
死锁的语句及相关的信息
*** (N) WAITING FOR THIS LOCK TO BE
GRANTED:
死锁的原因
28. 案例二 分析
• 死锁原因分析:
mysql> explain select * from GRP_OfflineNotify where UserId=852577338 and
GroupId=33990861 and NotifyType=10G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: GRP_OfflineNotify
type: ref
possible_keys:
IX_GRP_OfflineNotify_GroupId_NotifyType,IX_GRP_OfflineNotify_UserId
key: IX_GRP_OfflineNotify_GroupId_NotifyType
key_len: 5
ref: const,const
rows: 1 IX_GRP_OfflineNotify_Group
Extra: Using where Id_NotifyType
1 row in set (0.00 sec) (GroupId, NotifyType)
普通索引
29. 案例二 分析
• 原因归纳:
利用
IX_GRP_OfflineNotify_GroupId_NotifyType索引
去访问数据,而这个索引并不唯一,所以造成了
锁的区间较大,造成两个事务相互冲突死锁。
另外:
也可以参考show engine innodb statusG;中的
死锁部分中: WAITING FOR THIS LOCK TO BE
GRANTED
30. 案例二 分析
• 再回到问题上思考:
这个SQL是做什么的?
需要锁这么大的区间吗?
• 死锁语句:
DELETE FROM GRP_OfflineNotify
WHERE UserId = 852577338 AND
GroupId = 33990861 AND NotifyType = 10
DELETE FROM GRP_OfflineNotify
WHERE UserId = 825749088 AND
GroupId = 33990861 AND NotifyType = 10
31. 案例二 分析
• 非唯一索引Delete死锁原因:
root
brach1
5: col2= 3399086 ,col3=10,…
Leaf 1
Sec Ind1 PK loc 1001:col2= 3399086 ,col3=10,…
k
3399086 10 5 X 1006:col2= 3399086 ,col3=10,…
3399086 10 1001 X
3399086 10 1006 X
Table Redords
…
96257735 11 2
6
32. • 解决办法:
– 创建新索引:
create index IX_GRP_OfflineNotify_GroupId
_UserId_NotifyType on GRP_OfflineNotify(Grou
pId,UserId,NotifyType);
– 删除旧索引:
drop index IX_GRP_OfflineNotify_GroupId_
NotifyType on GRP_OfflineNotify;
33. • Innodb支持的锁类型:
– Record lock
– Next Key lock
– Gap lock
– Table lock
• 高并环境事务隔离级别:
READ-COMMITTED
34. Innodb锁的类型识别
• Gap-lock
– 这个锁影响Insert操作。
假设一个索引包括5,7利用这个索引访问过的
取出结果为5,7相关记录,但同时别的
session需要insert一个索引为6的索引,这个就
需要等待。
该锁在:READ-COMMITTED隔离级别下不工作。
• Table-lock
属于显式声明的,可以忽略。
35. 总结经验及运用
• Innodb索引分情况:
root
root
brach1
brach1
Leaf 1 Leaf 1
Sec Ind1 PK Pk Table other recoder
1 5 1 col2=‘wubx’, col3=‘775’,…
5 1 2 col2=‘liuxin’,col3=‘123’,…
… …
99 2 5 Col2=‘zbr’,col3=‘1000’…
Select * from tb where sec_ind1=1
36. 总结经验及运用
• 变长字段处理:
… Prefix(768B) …
COMACT
OverFlow
page
20 bytes
… …
DYNAMIC
OverFlow
page
38. 总结经验及运用
• 运用:
– 养成基于唯一索引访问数据的习惯
– Update操作不要去update主建
– 没有特殊的理由请给你的表添加一个无符号自
增列做为主建
– 如果大量的查询是基于条件的range操作,请把
这个索引做为第二索引