MySQL query performance using index
There is no perfect index for this:

WHERE cc.CampaignId = 30
  AND cc.CustomerId is not null
ORDER BY cc.PKCampaignCodeId


It is because the index cannot get past IS NOT NULL to get to the ORDER BY.

In general, 

WHERE a=1 AND b=2 ORDER BY c


can make good advantage of INDEX(a,b,c) or (b,a,c)

But, this cannot:

WHERE a=1 AND b!=2 ORDER BY c


(IS NOT NULL is similar to != 2 in that it is a "range", not a single value.)

To optimize this:

WHERE cc.CampaignId = 30
  AND cc.CustomerId is not null
ORDER BY cc.PKCampaignCodeId LIMIT 25 OFFSET 0


there are two possibilities.  The Optimizer will pick one, sometimes the less optimal one:

INDEX(CampaignId, CustomerId)
INDEX(CampaignId, PKCampaignCodeId)


The first will do the filtering, but still have to sort and limit.
The second will do some of the filtering, avoid the sort, but might have to look at a lot more than 25 rows.

There is no benefit of having all 3 columns in an index; the third column  will not be used.  (And, apparently, it causes trouble with your first SELECT.

More on creating indexes:  http://mysql.rjweb.org/doc.php/index_cookbook_mysql  (though it does not fully address all your questions)