Правильно расставленные индексы в mysql существенно ускоряют выборку из таблиц, сохраняя и выбирая данные из таблицы в необходимом порядке с более высокой скоростью. Создание первичных, уникальных и полнотекстовых индексов рассматривать пока не будем, итак всё ясно — они должны быть там, где они должны быть короче)) Тут я опишу логику создания простых и составных индексов mysql и порядок следования колонок в составных индексах.
Если выборка из таблицы происходит лишь по 1 колонке или сортируется лишь по 1 колонке, то для неё создается простой индекс, синтаксис такой:
CREATE INDEX index_name ON table_name(col_name); /* или */ ALTER TABLE table_name ADD INDEX index_name (col_name);
Если выборка по 1 и более колонкам и/или сортируется по 1 и более колонкам, то под запрос создаётся составной индекс для этих колонок, обычно(не факт! см. ниже) в том порядке, в котором они идут в запросе. Синтаксис:
CREATE INDEX index_name ON table_name(col_name_1, col_name_2, col_name_N); /* или */ ALTER TABLE table_name ADD INDEX index_name (col_name_1, col_name_2, col_name_N);
Однако, чтобы использовать возможности индексирования на полную катушку, не всегда следует создавать индексы в порядке следования их в запросе.
Селективность колонок, которые указываем в порядке первыми в составных индексах mysql, определяется количеством записей для этого значения. Чем меньше записей с одинковым значением, тем выше селективность и тем раньше такая колонка должна быть указана в индексе! Другими словами теперь: чем больше записей с разным значением колонки, тем выше селективность для этой колонки и тем раньше её следует указывать в составном индексе!
Определятся селективность просто:
SELECT COUNT(*) as `selectivity` FROM (SELECT * FROM `table_name` GROUP BY `col_name_1`) as `sel`; SELECT COUNT(*) as `selectivity` FROM (SELECT * FROM `table_name` GROUP BY `col_name_2`) as `sel`; SELECT COUNT(*) as `selectivity` FROM (SELECT * FROM `table_name` GROUP BY `col_name_N`) as `sel`;
Чем выше результаты этих запросов для каждой колонки, тем раньше её указываем в составном индексе. Вот и всё. Логика проста как мычание. Не надо заниматься глупостями и считать байты по колонкам. И вообще, это не сложно, сперва прочекать на порядок следования, а затем добавить составные индексы в нужном порядке. Проверка запроса на использование индексации выполняется командой EXPLAIN , а чтобы настроить логгирование медленных запросов и не использующих индексации обязательно почитайте про mysql_slow.log, настройте кеш запросов в mysql и ваша бд станет резкой как понос.
А чтобы не чекать всё ручками моя лень написала замечательный php скрипт для автоматической расстановки колонок в составных индексах, пользуйтесь на здоровье:
<?php
ob_start();
session_start();
header("Cache-Control: no-cache");
header("Expires: ".gmdate("D, d M Y H:i:s", time()-86400)." GMT");
class DB
{
protected $connection;
public $insert_id;
public $affected_rows;
public function __construct($host, $username, $password, $db_name)
{
$this->connection = @new mysqli($host, $username, $password, $db_name);
if (!$this->connection) {
throw new Exception('Could not connect to DB ');
}
if ($this->connection->connect_error) {
die('Ошибка подключения (' . $this->connection->connect_errno . ') '
. $this->connection->connect_error);
}
$this->connection->set_charset("utf8");
$this->connection->query('SET time_zone="' . date('P') . '"');
}
public function query($sql)
{
if (!$this->connection) {
return false;
}
$result = $this->connection->query($sql);
if (mysqli_error($this->connection)) {
throw new Exception(mysqli_error($this->connection));
}
$this->insert_id = $this->connection->insert_id;
$this->affected_rows = $this->connection->affected_rows;
return $result;
}
public function esc($str)
{
$str = strtr($str, array(
chr(0) => '',
chr(1) => '',
chr(2) => '',
chr(3) => '',
chr(4) => '',
chr(5) => '',
chr(6) => '',
chr(7) => '',
chr(8) => '',
chr(9) => '',
chr(11) => '',
chr(12) => '',
chr(14) => '',
chr(15) => '',
chr(16) => '',
chr(17) => '',
chr(18) => '',
chr(19) => '',
chr(20) => '',
chr(21) => '',
chr(22) => '',
chr(23) => '',
chr(24) => '',
chr(25) => '',
chr(26) => '',
chr(27) => '',
chr(28) => '',
chr(29) => '',
chr(30) => '',
chr(31) => ''
));
return mysqli_escape_string($this->connection, $str);
}
}
if (isset($_POST['db_host'])) {
$_SESSION = $_POST;
$db = new DB($_POST['db_host'], $_POST['db_user'], $_POST['db_pass'], $_POST['db_name']);
if (isset($_POST['new_connect'])) {
$q = $db->query("SHOW TABLES");
echo '<div class="form-group mt-2">
<select onchange="selectTable(this.value)" name="db_table" class="custom-select custom-select-sm">
<option value="" selected="">Выберете таблицу</option>';
while ($table = $q->fetch_array()) {
echo '<option value="'.$table[0].'">'.$table[0].'</option>';
}
exit('</select></div><div id="columns"></div>');
} elseif (isset($_POST['show_columns'])) {
$q = $db->query("SHOW INDEXES FROM `".$db->esc($_POST['show_columns'])."`");
echo '<table class="table table-striped"><thead><tr>';
$names = $q->fetch_assoc();
foreach ($names as $key => $value) {
echo '<th>'.$key.'</th>';
}
$q->data_seek(0);
echo '</tr></thead><tbody>';
while ($row = $q->fetch_assoc()) {
echo '<tr>';
foreach ($row as $key => $value) {
echo '<td>'.$value.($key == 'Key_name' ? '<button onclick="
$(\'#delKey .modal-body\').html(\'ALTER TABLE `'.$db->esc($_POST['show_columns']).'` DROP INDEX `'.$value.'`\');
$(\'#delKey\').modal();
" type="button" class="close" style="font-size:0.9rem;">×</button>' : '').'</td>';
}
echo '</tr>';
}
echo '</tbody></table>';
$q = $db->query("SHOW COLUMNS FROM `".$db->esc($_POST['show_columns'])."`");
echo '<form id="cols"><table class="table table-striped"><thead><tr>';
$names = $q->fetch_assoc();
foreach ($names as $key => $value) {
echo '<th>'.$key.'</th>';
}
echo '<th>Selectivity</th>';
$q->data_seek(0);
echo '</tr></thead><tbody>';
while ($row = $q->fetch_assoc()) {
echo '<tr>';
$i = 0;
$sel = 0;
foreach ($row as $value) {
if ($i == 0) {
$sel = $db->query("SELECT COUNT(*) as `selectivity` FROM (SELECT * FROM `".$db->esc($_POST['show_columns'])."` GROUP BY `".$value."`) as `sel`")->fetch_object()->selectivity;
echo '<td><div class="custom-control custom-checkbox">
<input onchange="changeCols()" type="checkbox" class="custom-control-input" id="'.$value.'" name="cols['.$value.']">
<label class="custom-control-label" for="'.$value.'">'.$value.'</label>
</div></td>';
} else {
echo '<td>'.$value.'</td>';
}
$i++;
}
echo '<td>'.$sel.'</td></tr>';
}
exit('</tbody></table></form><div id="selectivity"></div>');
} elseif (isset($_POST['cols'])) {
$cols = array();
foreach ($_POST['cols'] as $key => $value) {
$s = $db->query("SELECT COUNT(*) as `selectivity` FROM (SELECT * FROM `".$db->esc($_POST['db_table'])."` GROUP BY `".$db->esc($key)."`) as `sel`")->fetch_object()->selectivity;
$cols[$s.$key] = $key;
}
krsort($cols, SORT_NUMERIC);
echo '<form id="addindex">
<div class="input-group input-group-sm mb-5">
<div class="input-group-prepend">
<span class="input-group-text">SQL</span>
</div>
<input type="text" class="form-control" name="sql" value="ALTER TABLE `' . $db->esc($_POST['db_table']) . '` ADD INDEX `'.implode('_', array_unique(explode('_', implode('_', $cols)))).'` (`' . implode('`, `',$cols) . '`);">
<div class="input-group-append">
<button onclick="addIndex()" type="button" class="btn btn-primary">+</button>
</div>
</div>
</form>';
exit;
} elseif (isset($_POST['sql'])) {
$db->query($_POST['sql']);
exit;
}
exit;
}
?><!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>index-creater</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<style>
.preloader-body{display:none;position:fixed;left:0px;top:0px;right:0px;z-index:2147483647;width:100%;height:100%;background:rgba(0, 0, 0, 0.1);}
.cssload-loader{position:absolute;left:calc(50% - 100px);top:calc(50% - 100px);width:200px;height:200px;border-radius:50%;}
.cssload-inner{position:absolute;width:100%;height:100%;box-sizing:border-box;border-radius:50%}.cssload-inner.cssload-one{left:0;top:0;animation:cssload-rotate-one 1.15s linear infinite;border-bottom:3px solid #000}@keyframes cssload-rotate-one{0%{transform:rotateX(35deg) rotateY(-45deg) rotateZ(0)}100%{transform:rotateX(35deg) rotateY(-45deg) rotateZ(360deg)}}.cssload-inner.cssload-two{right:0;top:0;animation:cssload-rotate-two 1.15s linear infinite;border-right:3px solid #000}@keyframes cssload-rotate-two{0%{transform:rotateX(50deg) rotateY(10deg) rotateZ(0)}100%{transform:rotateX(50deg) rotateY(10deg) rotateZ(360deg)}}.cssload-inner.cssload-three{right:0;bottom:0;animation:cssload-rotate-three 1.15s linear infinite;border-top:3px solid #000}@keyframes cssload-rotate-three{0%{transform:rotateX(35deg) rotateY(55deg) rotateZ(0)}100%{transform:rotateX(35deg) rotateY(55deg) rotateZ(360deg)}}.hide{display:none;}
.table td, .table th{padding:2px;border:1px solid #dee2e6;font-size:12px;}
.custom-control-label::before,.custom-control-label::after{top:0;cursor:pointer;}
.custom-control-label{cursor:pointer;}
.custom-control{min-height:0;}
</style>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</head>
<body>
<div class="preloader-body">
<div class="cssload-loader">
<div class="cssload-inner cssload-one"></div>
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
</div>
<div class="container-fluid">
<form id="connect" class="mt-2">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">БД</span>
</div>
<input type="text" class="form-control" placeholder="Хост" name="db_host" value="<?=(isset($_SESSION['db_host']) ? $_SESSION['db_host'] : 'localhost')?>">
<input type="text" class="form-control" placeholder="Пользователь" name="db_user" value="<?=(isset($_SESSION['db_user']) ? $_SESSION['db_user'] : '')?>">
<input type="password" class="form-control" placeholder="Пароль" name="db_pass" value="<?=(isset($_SESSION['db_pass']) ? $_SESSION['db_pass'] : '')?>">
<input type="text" class="form-control" placeholder="Имя базы" name="db_name" value="<?=(isset($_SESSION['db_name']) ? $_SESSION['db_name'] : '')?>">
<div class="input-group-append">
<button type="button" class="btn btn-primary">Connect</button>
</div>
</div>
</form>
<div id="results"></div>
</div>
<div class="modal fade" id="delKey">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Подтвердите</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button onclick="rmIndex()" type="button" class="btn btn-danger" data-dismiss="modal">Да</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Нет</button>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script>
$("#connect button").click(() => {
$(".preloader-body").show();
$.post({
url: "?",
data: $("#connect").serialize() + "&new_connect",
dataType: "html",
cache: false
})
.always(res => {
$(".preloader-body").hide();
$("#results").html(res);
})
});
function selectTable (val) {
if (!val) {
return $("#columns").empty();
}
$(".preloader-body").show();
$.post({
url: "?",
data: $("#connect").serialize() + "&show_columns=" + val,
dataType: "html",
cache: false
})
.always(res => {
$(".preloader-body").hide();
$("#columns").html(res);
})
}
function changeCols (val) {
$(".preloader-body").show();
$.post({
url: "?",
data: $("#connect").serialize() + "&" + $("#cols").serialize() + "&db_table=" + $("#results select[name=db_table]").val(),
dataType: "html",
cache: false
})
.always(res => {
$(".preloader-body").hide();
$("#selectivity").html(res);
})
}
function addIndex () {
$("#addindex button").hide();
$(".preloader-body").show();
$.post({
url: "?",
data: $("#connect").serialize() + "&" + $("#addindex").serialize(),
dataType: "html",
cache: false
})
.always(res => {
$("#results select[name=db_table]").trigger("change");
$("#addindex button").show();
})
}
function rmIndex() {
$(".preloader-body").show();
$.post({
url: "?",
data: $("#connect").serialize() + "&sql=" + $("#delKey .modal-body").html(),
dataType: "html",
cache: false
})
.always(res => {
$("#results select[name=db_table]").trigger("change");
})
}
</script>
</body>
</html>
Принцип работы на картинках:

